emWeb Webserver Single-Task (Sample)

From SEGGER Knowledge Base
Jump to navigation Jump to search
IP_Webserver_SingleTask_IPv4_IPv6_Sample.c
Requires modifications No
Download IP_Webserver_SingleTask_IPv4_IPv6_Sample.c

This Sample demonstrates how the Web server can be used with one task. The sample can be used with IPv4 and IPv6.

All connection requests (sockets) are queued and processed sequentially. Every socket is closed after processing. This sample is suitable for targets with limited memory.

NOTE: In projects where multiple clients connect in parallel, the standard Web server sample (IP_WebServer_Sample.c) should be used.

Code

/*********************************************************************
*                   (c) SEGGER Microcontroller GmbH                  *
*                        The Embedded Experts                        *
*                           www.segger.com                           *
**********************************************************************

-------------------------- END-OF-HEADER -----------------------------

Purpose : Sample program for emWeb.
          The sample demonstrates how the Web server can be used with
          one task. The sample can be used with IPv4 and IPv6.
          To enable IPv6 USE_V6 has to be set.
          All connection requests (sockets) are queued and processed
          sequentially. Every socket is closed after processing.
          This sample is suitable for targets with limited memory.

          Please note, in projects where multiple clients connect
          in parallel, the standard Web server sample
          (IP_Webserver_Sample.c) should be used.

Additional Information:

          IP_WebserverSample_SingleTask_IPv4_IPv6.c has been tested
          with the following configuration:

          ===========
          - emNet:
          ===========
          #define ALLOC_SIZE 0x3000                         // 12KBytes RAM.
          mtu = 1280;                                       // 576 is minimum for IPv4, 1280 minimum for IPv6, 1500 is max. for Ethernet.
          IP_SetMTU(0, mtu);                                // Maximum Transmission Unit is 1500 for Ethernet by default.
          IP_AddBuffers(4, 256);                            // Small buffers.
          IP_AddBuffers(4, mtu + 16);                       // Big buffers. Size should be mtu + 16 byte for Ethernet header (2 bytes type, 2*6 bytes MAC, 2 bytes padding).
          IP_ConfTCPSpace(2 * (mtu - 40), 2 * (mtu - 40));  // Define the TCP Tx and Rx window size. At least Tx space for 2*(mtu-40/60) for two full TCP packets is needed.

          ======================
          - emWeb:
          ======================
          The used memory pool (WEBS_ALLOC_SIZE) of 1536 bytes
          consists of:

          WEBS_IN_BUFFER_SIZE              256              // Buffer size should be at least 256 bytes.
          WEBS_OUT_BUFFER_SIZE            1420              // The buffer size is derived from the used MTU.
                                                            // Please note, that the default is 1460 bytes and has to changed manually in WEBS_Conf.h.
                                                            // The size of the out buffer should aligned with the used MTU:
                                                            //       MTU - header size of the used protocols.
                                                            // E.g.: MTU = 576. IPv4 header = 20 bytes. TCP header size = 20 bytes.
                                                            //       MTU - 40 bytes.
                                                            // If IPv6 is the mostly used protocol, the buffer should be adapted.
                                                            // E.g.: MTU = 1280. IPv4 header = 40 bytes. TCP header size = 20 bytes.
                                                            //       MTU - 60 bytes.
                                                            // Please note, the size of the out buffer can be smaller as the MTU - protocol overhead,
                                                            // but a smaller out buffer size will limit the performance.
          WEBS_PARA_BUFFER_SIZE            256              // Required for dynamic content parameter handling.
          WEBS_FILENAME_BUFFER_SIZE         64
          WEBS_MAX_ROOT_PATH_LEN            12
          WEBS_SUPPORT_UPLOAD                0
          WEBS_USE_AUTH_DIGEST               0
          + Read-only file system

          The size of the memory pool can be fine tuned according
          to your configuration using IP_WEBS_CountRequiredMem().

          A typical webpage used for the tests consists of 3 elements:
          - 1 x html file
          - 1 x CSS file
          - 1 x gif

          Tested with the following browsers:
            - Mozilla Firefox 54.0.1
            - Google Chrome 60.0.3112.90
            - Microsoft Edge
            - Internet Explorer 11
            - Safari 9.1.2

          Features like Server-Sent-Events will work, since a reconnect
          in cases of connection loss is provided, but this erodes the
          idea of a persistent connection.
*/

#include <stdio.h>
#include <stdlib.h>
#include "RTOS.h"
#include "BSP.h"
#include "IP.h"
#include "IP_Webserver.h"
#include "WEBS_Conf.h"        // Stack size depends on configuration


/*********************************************************************
*
*       Local Types
*
**********************************************************************
*/
typedef struct {
  int hSock;
} _CONN_QUEUE;


/*********************************************************************
*
*       Local defines, configurable
*
**********************************************************************
*/
#define USE_RX_TASK   0                // 0: Packets are read in ISR, 1: Packets are read in a task of its own.

#define USE_IPV6      IP_SUPPORT_IPV6  // Open an additional listening IPv6 port.

//
// Web server and IP stack
//
#define SERVER_PORT              80
#define WEBS_ALLOC_SIZE        2560   // NumBytes required from memory pool. Should be fine tuned according to your configuration using IP_WEBS_CountRequiredMem() .
#define MAX_CONNECTION_QUEUE     20   // Max. number of connections we accept and queue.

//
// Task priorities.
//
enum {
  TASK_PRIO_WEBS = 150
  ,TASK_PRIO_IP_TASK           // Priority should be higher than all IP application tasks.
#if USE_RX_TASK
  ,TASK_PRIO_IP_RX_TASK        // Must be the highest priority of all IP related tasks.
#endif
};

//
// UDP discover
//
#define ETH_UDP_DISCOVER_PORT    50020
#define PACKET_SIZE              0x80

/*********************************************************************
*
*       Static data
*
**********************************************************************
*/

static U32 _aPool[WEBS_ALLOC_SIZE / sizeof(int)];  // Memory pool for the Web server child tasks.

static IP_HOOK_ON_STATE_CHANGE _StateChangeHook;
static int                     _IFaceId;

//
// Task stacks and Task-Control-Blocks.
//
static OS_STACKPTR int _IPStack[TASK_STACK_SIZE_IP_TASK/sizeof(int)];       // Stack of the IP_Task.
static OS_TASK         _IPTCB;                                              // Task-Control-Block of the IP_Task.

#if USE_RX_TASK
static OS_STACKPTR int _IPRxStack[TASK_STACK_SIZE_IP_RX_TASK/sizeof(int)];  // Stack of the IP_RxTask.
static OS_TASK         _IPRxTCB;                                            // Task-Control-Block of the IP_RxTask.
#endif

static long               _hSockListenV4;
static IP_TCP_ACCEPT_HOOK _HandleIPv4;

#if USE_IPV6
static long               _hSockListenV6;
static IP_TCP_ACCEPT_HOOK _HandleIPv6;
#endif

//
// Connection queue
//
static _CONN_QUEUE        _aQueueConn[MAX_CONNECTION_QUEUE];  // Like a ring buffer
static int                _iQueuePop;                         // Points to pos. in queue where to pop the next queued connection
static int                _iQueuePush;                        // Points to pos. in queue where to push the next connection
static unsigned           _QueueFillCnt;                      // Used to avoid the need of division for "free space" in queue, as an entry is not 1 byte in size but multiple ones, so pure pointer arithmetic does not do the trick
static WEBS_CONTEXT       _WebsContext;

//
// File system info
//
static const IP_FS_API*   _pFS_API;

/*********************************************************************
*
*       Prototypes
*
**********************************************************************
*/
#ifdef __cplusplus
extern "C" {     /* Make sure we have C-declarations in C++ programs */
#endif
void MainTask(void);
#ifdef __cplusplus
}
#endif

/*********************************************************************
*
*       Static code
*
**********************************************************************
*/

/*********************************************************************
*
*       _OnStateChange()
*
* Function description
*   Callback that will be notified once the state of an interface
*   changes.
*
* Parameters
*   IFaceId   : Zero-based interface index.
*   AdminState: Is this interface enabled ?
*   HWState   : Is this interface physically ready ?
*/
static void _OnStateChange(unsigned IFaceId, U8 AdminState, U8 HWState) {
  //
  // Check if this is a disconnect from the peer or a link down.
  // In this case call IP_Disconnect() to get into a known state.
  //
  if (((AdminState == IP_ADMIN_STATE_DOWN) && (HWState == 1)) ||  // Typical for dial-up connection e.g. PPP when closed from peer. Link up but app. closed.
      ((AdminState == IP_ADMIN_STATE_UP)   && (HWState == 0))) {  // Typical for any Ethernet connection e.g. PPPoE. App. opened but link down.
    IP_Disconnect(IFaceId);                                       // Disconnect the interface to a clean state.
  }
}

/*********************************************************************
*
*       _closesocket()
*
*  Function description
*    Wrapper for closesocket()
*/
static int _closesocket(long pConnectionInfo) {
  int r;
  struct linger Linger;
  long hSock;

  hSock = (long)pConnectionInfo;
  Linger.l_onoff  = 1;  // Enable linger for this socket to verify that all data is send.
  Linger.l_linger = 1;  // Linger timeout in seconds
  setsockopt(hSock, SOL_SOCKET, SO_LINGER, &Linger, sizeof(Linger));
  r = closesocket(hSock);
  IP_Logf_Application("WS: Socket no. %d closed.", hSock);
  return r;
}

/*********************************************************************
*
*       _Recv()
*
*  Function description
*    Wrapper for recv()
*/
static int _Recv(unsigned char *buf, int len, void *pConnectionInfo) {
  int r;

  r = recv((long)pConnectionInfo, (char *)buf, len, 0);
  return r;
}

/*********************************************************************
*
*       _Send()
*
*  Function description
*    Wrapper for send()
*/
static int _Send(const unsigned char *buf, int len, void* pConnectionInfo) {
  int r;

  r = send((long)pConnectionInfo, (const char *)buf, len, 0);
  return r;
}

/*********************************************************************
*
*       WEBS_IP_API
*
*  Description
*   IP related function table
*/
static const WEBS_IP_API _Webs_IP_API = {
  _Send,
  _Recv
};

/*********************************************************************
*
*       _Alloc()
*
*  Function description
*    Wrapper for Alloc(). (emNet: IP_MEM_Alloc())
*/
static void * _Alloc(U32 NumBytesReq) {
  return IP_AllocEx(_aPool, NumBytesReq);
}

/*********************************************************************
*
*       _Free()
*
*  Function description
*    Wrapper for Alloc(). (emNet: IP_MEM_Alloc())
*/
static void _Free(void *p) {
  IP_Free(p);
}

/*********************************************************************
*
*       WEBS_SYS_API
*
*  Description
*   System related function table
*/
static const WEBS_SYS_API _Webs_SYS_API = {
  _Alloc,
  _Free
};

/*********************************************************************
*
*       _CreateSocket()
*
* Function description
*   Creates a socket for the requested protocol family.
*
* Parameter
*   IPProtVer - Protocol family which should be used (PF_INET or PF_INET6).
*
* Return value
*   O.K. : Socket handle.
*   Error: SOCKET_ERROR .
*/
static int _CreateSocket(unsigned IPProtVer) {
  int hSock;

  hSock = SOCKET_ERROR;
#if USE_IPV6
  //
  // Create IPv6 socket
  //
  if (IPProtVer == PF_INET6) {
    hSock = socket(AF_INET6, SOCK_STREAM, 0);
  }
#endif
  //
  // Create IPv4 socket
  //
  if (IPProtVer == AF_INET) {
    hSock = socket(AF_INET, SOCK_STREAM, 0);
  }
  return hSock;
}

/*********************************************************************
*
*       _BindAtTcpPort()
*
* Function description
*   Binds a socket to a port.
*
* Parameter
*   IPProtVer - Protocol family which should be used (PF_INET or PF_INET6).
*   hSock     - Socket handle
*   Port      - Port which should be to wait for connections.
*
* Return value
*   O.K. : == 0
*   Error: != 0
*/
static int _BindAtTcpPort(unsigned IPProtVer, int hSock, U16 LPort) {
  int r;

  r = -1;

  //
  // Bind it to the port
  //
#if USE_IPV6
  if (IPProtVer == PF_INET6) {
    struct sockaddr_in6 Addr;

    IP_MEMSET(&Addr, 0, sizeof(Addr));
    Addr.sin6_family      = AF_INET6;
    Addr.sin6_port        = htons(LPort);
    Addr.sin6_flowinfo   = 0;
    IP_MEMSET(&Addr.sin6_addr, 0, 16);
    Addr.sin6_scope_id   = 0;
    r = bind(hSock, (struct sockaddr*)&Addr, sizeof(Addr));
  }
#endif
  if (IPProtVer == AF_INET) {
    struct sockaddr_in Addr;

    IP_MEMSET(&Addr, 0, sizeof(Addr));
    Addr.sin_family      = AF_INET;
    Addr.sin_port        = htons(LPort);
    Addr.sin_addr.s_addr = INADDR_ANY;
    r = bind(hSock, (struct sockaddr*)&Addr, sizeof(Addr));
  }
  return r;
}

/*********************************************************************
*
*       _ListenAtTcpPort()
*
* Function description
*   Creates a socket, binds it to a port and sets the socket into
*   listening state.
*
* Parameter
*   IPProtVer - Protocol family which should be used (PF_INET or PF_INET6).
*   Port      - Port which should be to wait for connections.
*
* Return value
*   O.K. : Socket handle.
*   Error: SOCKET_ERROR .
*/
static int _ListenAtTcpPort(unsigned IPProtVer, U16 Port) {
  int hSock;
  int r;

  //
  // Create socket
  //
  hSock = _CreateSocket(IPProtVer);
  if (hSock != SOCKET_ERROR) {
    //
    // Bind it to the port
    //
    r = _BindAtTcpPort(IPProtVer, hSock, Port);
    //
    // Start listening on the socket.
    //
    if (r != 0) {
      hSock = SOCKET_ERROR;
    } else {
      r = listen(hSock, MAX_CONNECTION_QUEUE);
      if (r != 0) {
        hSock = SOCKET_ERROR;
      }
    }
  }
  return hSock;
}

/*********************************************************************
*
*       _ConnQueuePop()
*
*  Function description
*    Pops an entry from the connection queue, if there is anything to be popped
*
*  Return value
*    >= 0  O.K., entry popped
*     < 0  Error, no entry has been popped (most likely because there are no more entries)
*
*  Notes
*    (1) Called from multiple threads with interrupts and task changes enabled
*/
static int _ConnQueuePop(int* pSock) {
  int r;
  int i;

  r = -1;
  OS_EnterRegion();  // Make sure that queue is not touched by Webserver accept callback or any other child task, while we modify it
  if (_QueueFillCnt) {
    i = _iQueuePop;
    *pSock = _aQueueConn[i].hSock;
    i++;
    if (i >= MAX_CONNECTION_QUEUE) {  // Handle wrap-around
      i = 0;
    }
    _iQueuePop = i;
    _QueueFillCnt--;
    r = 0;
  }
  OS_LeaveRegion();
  return r;
}

/*********************************************************************
*
*       _ConnQueuePush()
*
*  Function description
*    Pushes an entry onto the connection queue, if there is any space left
*
*  Return value
*    >= 0  O.K., entry pushed
*     < 0  Error, entry not pushed (most likely because queue is full)
*
*  Notes
*    (1) Called from multiple threads with interrupts and task changes enabled
*/
static int _ConnQueuePush(int Sock) {
  int r;
  int i;

  r = -1;
  OS_EnterRegion();  // Make sure that queue is not touched by Webserver accept callback or any other child task, while we modify it
  if (_QueueFillCnt < MAX_CONNECTION_QUEUE) {
    i = _iQueuePush;
    _aQueueConn[i].hSock = Sock;
    i++;
    if (i >= MAX_CONNECTION_QUEUE) {  // Handle wrap-around
      i = 0;
    }
    _iQueuePush = i;
    _QueueFillCnt++;
    r = 0;
  }
  OS_LeaveRegion();
  return r;
}

/*********************************************************************
*
*       _cbAccept()
*
*  Function description
*    Called by IP stack each time a new connection from a client is opened.
*
*  Parameters
*    hSock   : Accepted child socket.
*    pInfo   : Connection information.
*    pContext: Custom context as registered with the accept hook.
*
*  Notes
*    (1) Must not block under any circumstances (e.g. only call send() for non-blocking sockets etc.)
*    (2)
*/
static void _cbAccept(int hSock, IP_TCP_ACCEPT_INFO* pInfo, void* pContext) {
  int Opt;
  int r;

  //
  // We queue the connections in a static list.
  //
  IP_USE_PARA(pContext);
  IP_USE_PARA(pInfo);
  IP_Logf_Application("WS: New sock %d.", hSock);
  //
  // Enable keep alive probes on new socket.
  //
  Opt   = 1;
  setsockopt(hSock, SOL_SOCKET, SO_KEEPALIVE, &Opt, sizeof(Opt));
  //
  // Connection cannot be handled immediately? => Queue it, if possible
  //
  IP_Logf_Application("WS: Queue sock %d", hSock);
  r = _ConnQueuePush(hSock);
  if (r < 0) {
    IP_Logf_Application("WS: Cannot queue sock %d", hSock);
    _closesocket(hSock);
  }
}

/*********************************************************************
*
*       _HandleConnection
*/
static void _HandleConnection(WEBS_CONTEXT* pWebsContext) {
  int          hSock;
  int          NumBytes;
  int          r;
  int          v;

  do {
    //
    // Check if we have a queued socket.
    //
    v = _ConnQueuePop(&hSock);
    if (v >= 0) {
      //
      // Check if data is available in the socket buffer
      //
      getsockopt(hSock, SOL_SOCKET, SO_RXDATA, &NumBytes, sizeof(NumBytes));
      if (NumBytes > 0) {
        r = IP_WEBS_ProcessLastEx(pWebsContext, (void*)hSock, NULL);
        IP_Logf_Application("WS: Sock %d, Return value: %d", hSock, r);
      }
      _closesocket(hSock);
    }
  } while (v >= 0);
}

/*********************************************************************
*
*       _WebServerInit
*/
static void _WebServerInit(void) {
  WEBS_BUFFER_SIZES BufferSizes;
  U32               NumBytes;

  //
  // Assign file system
  //
  _pFS_API = &IP_FS_ReadOnly;  // To use a a real filesystem like emFile replace this line.
//  _pFS_API = &IP_FS_emFile;    // Use emFile
//  IP_WEBS_AddUpload();         // Enable upload
  //
  // Configure buffer size.
  //
  IP_MEMSET(&BufferSizes, 0, sizeof(BufferSizes));
  BufferSizes.NumBytesInBuf       = WEBS_IN_BUFFER_SIZE;
#if USE_IPV6
  BufferSizes.NumBytesOutBuf      = IP_TCP_GetMTU(_IFaceId) - 40 - 20;  // Use max. MTU configured for the last interface added minus IPv6(40 bytes)/TCP(20 bytes) headers.
                                                                        // Calculation for the memory pool is done under assumption of the best case headers with - 60 bytes for IPv6.
#else
  BufferSizes.NumBytesOutBuf      = IP_TCP_GetMTU(_IFaceId) - 20 - 20;  // Use max. MTU configured for the last interface added minus IPv4(20 bytes)/TCP(20 bytes) headers.
                                                                        // Calculation for the memory pool is done under assumption of the best case headers with - 40 bytes for IPv4.
#endif
  BufferSizes.NumBytesParaBuf     = WEBS_PARA_BUFFER_SIZE;
  BufferSizes.NumBytesFilenameBuf = WEBS_FILENAME_BUFFER_SIZE;
  BufferSizes.MaxRootPathLen      = WEBS_MAX_ROOT_PATH_LEN;
  //
  // Configure the size of the buffers used by the Webserver child tasks.
  //
  IP_WEBS_ConfigBufSizes(&BufferSizes);
  //
  // Check memory pool size.
  //
  NumBytes = IP_WEBS_CountRequiredMem(NULL);     // Get NumBytes for internals of one child thread.
  NumBytes = NumBytes + 64;                      // Total amount + some bytes for managing a memory pool.
  IP_Logf_Application("WEBS: Using a memory pool of %lu bytes. Min. required: %lu", sizeof(_aPool), NumBytes);
  if (NumBytes > sizeof(_aPool)) {
    IP_Warnf_Application("WEBS: Memory pool should be at least %lu bytes.", NumBytes);
  }
  //
  // Give the stack some more memory to enable the dynamical memory allocation for the Web server child tasks
  //
  IP_AddMemory(_aPool, sizeof(_aPool));
  //
  // Get a valid IPv4 socket listening on port 80
  //
  while (1) {
    _hSockListenV4 = _ListenAtTcpPort(PF_INET, SERVER_PORT);
    if (_hSockListenV4 == SOCKET_ERROR) {
      OS_Delay(2000);
      continue;  // Error, try again.
    }
    break;
  }
  IP_TCP_Accept(&_HandleIPv4, &_cbAccept, _hSockListenV4, NULL);
#if USE_IPV6
  //
  // Get a valid IPv6 socket listening on port 80
  //
  while (1) {
    _hSockListenV6 = _ListenAtTcpPort(PF_INET6, SERVER_PORT);
    if (_hSockListenV6 == SOCKET_ERROR) {
      OS_Delay(2000);
      continue;  // Error, try again.
    }
    break;
  }
  IP_TCP_Accept(&_HandleIPv6, &_cbAccept, _hSockListenV6, NULL);
#endif
  //
  // Initialize the context of the Web server task.
  //
  IP_WEBS_Init(&_WebsContext, &_Webs_IP_API, &_Webs_SYS_API, _pFS_API, &WebsSample_Application);
}

/*********************************************************************
*
*       _OnRx
*
*  Function description
*    Discover client UDP callback. Called from stack
*    whenever we get a discover request.
*
*  Return value
*    IP_RX_ERROR  if packet is invalid for some reason
*    IP_OK        if packet is valid
*/
#if ETH_UDP_DISCOVER_PORT
static int _OnRx(IP_PACKET *pInPacket, void *pContext) {
  char *      pInData;
  IP_PACKET * pOutPacket;
  char *      pOutData;
  U32         TargetAddr;
  U32         IPAddr;
  unsigned    IFaceId;

  (void)pContext;

  IFaceId = IP_UDP_GetIFIndex(pInPacket);  // Find out the interface that the packet came in.
  IPAddr  = htonl(IP_GetIPAddr(IFaceId));
  if (IPAddr == 0) {
    goto Done;
  }
  pInData = (char*)IP_UDP_GetDataPtr(pInPacket);
  if (memcmp(pInData, "Discover", 8)) {
    goto Done;
  }
  //
  // Alloc packet
  //
  pOutPacket = IP_UDP_AllocEx(IFaceId, PACKET_SIZE);
  if (pOutPacket == NULL) {
    goto Done;
  }
  //
  // Fill packet with data
  //
  pOutData = (char*)IP_UDP_GetDataPtr(pOutPacket);
  IP_UDP_GetSrcAddr(pInPacket, &TargetAddr, sizeof(TargetAddr));    // Send back Unicast
  memset(pOutData, 0, PACKET_SIZE);
  strcpy(pOutData + 0x00, "Found");
  IPAddr = htonl(IP_GetIPAddr(IFaceId));
  memcpy(pOutData + 0x20, (void*)&IPAddr, 4);      // 0x20: IP address
  IP_GetHWAddr(IFaceId, (U8*)pOutData + 0x30, 6);  // 0x30: MAC address
  //
  // Send packet
  //
  IP_UDP_SendAndFree(IFaceId, TargetAddr, ETH_UDP_DISCOVER_PORT, ETH_UDP_DISCOVER_PORT, pOutPacket);
Done:
  return IP_OK;
}
#endif

/*********************************************************************
*
*       Global functions
*
**********************************************************************
*/

/*********************************************************************
*
*       MainTask
*/
void MainTask(void) {
  IP_Init();
  _IFaceId = IP_INFO_GetNumInterfaces() - 1;                                           // Get the last registered interface ID as this is most likely the interface we want to use in this sample.
  //
  // Start TCP/IP task
  //
  OS_CREATETASK(&_IPTCB,   "IP_Task",   IP_Task,   TASK_PRIO_IP_TASK,    _IPStack);
#if USE_RX_TASK
  OS_CREATETASK(&_IPRxTCB, "IP_RxTask", IP_RxTask, TASK_PRIO_IP_RX_TASK, _IPRxStack);  // Start the IP_RxTask, optional.
#endif
  IP_AddStateChangeHook(&_StateChangeHook, _OnStateChange);                            // Register hook to be notified on disconnects.
  IP_Connect(_IFaceId);                                                                // Connect the interface if necessary.
  //
  // IPv4 address configured ?
  //
  while (IP_IFaceIsReadyEx(_IFaceId) == 0) {
    BSP_ToggleLED(0);
    OS_Delay(200);
  }
  BSP_ClrLED(0);
  OS_SetPriority(OS_GetTaskID(), TASK_PRIO_WEBS);                                  // This task has highest prio!
  OS_SetTaskName(OS_GetTaskID(), "IP_WebServer");
#if ETH_UDP_DISCOVER_PORT
  //
  // Open UDP port ETH_UDP_DISCOVER_PORT for emNet discover
  //
  IP_UDP_Open(0L /* any foreign host */,  ETH_UDP_DISCOVER_PORT, ETH_UDP_DISCOVER_PORT,  _OnRx, 0L);
#endif
  IP_WEBS_X_SampleConfig();  // Load a web server sample config that might add other resources like REST.
  _WebServerInit();
  //
  // Handle connection requests.
  //
  while(1) {
    _HandleConnection(&_WebsContext);
    BSP_ToggleLED(0);
    OS_Delay(50);
  }
}

/*************************** End of file ****************************/