全部例程

HTTP Server

W5500 其他标签

2025/02/12 更新

本篇文章我们将详细介绍如何在W5500芯片上面实现HTTP Server功能,并通过实战例程,为大家讲解如何通过浏览器访问W5500的web页面。

该例程用到的其他网络协议,例如DHCP, 请参考相关章节。有关 W5500 的初始化过程,请参考Network install,这里将不再赘述。

HTTP协议简介

HTTP(超文本传输协议,HyperText Transfer Protocol)是一种用于分布式、协作式、超媒体信息系统的应用层协议, 基于 TCP/IP 通信协议来传递数据,是万维网(WWW)的数据通信的基础。设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法,通过 HTTP 或者 HTTPS 协议请求的资源由统一资源标识符(Uniform Resource Identifiers,URI)来标识。 以上是HTTP协议的简介,如想深入了解该协议,请参考mozilla网站上的介绍:HTTP 概述-HTTP|MDN

HTTP协议特点

  • 基于请求-响应模型:客户端发起请求,服务器处理后返回响应。例如,用户在浏览器输入网址时, 浏览器会向对应服务器发送HTTP请求,服务器返回网页内容。
  • 无状态性: HTTP本身不保存请求之间的状态,每次请求独立。但可以通过Cookie、Session等机制实现状态保持。
  • 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求并收到客户的应答后,便立即断开连接。

HTTP Server

W5500使用HTTP Server模式可以进行以下几种应用:

  • 设备配置和管理: 通过浏览器访问W5500提供的WEB界面,实现网络配置,系统参数调整,固件升级等操作。
  • 实时监控和数据展示:通过浏览器访问W5500提供的WEB页面,实时监控传感器数据,状态信息,以及查看工作日志等。
  • 远程控制:通过浏览器访问W5500提供的WEB页面进行远程控制设备,如开关等。

HTTP协议的基本工作流程

HTTP的请求-响应模型通常由以下几个步骤组成

  1. 建立连接:客户端与服务器之间基于TCP/IP协议建立连接。
  2. 发送请求:客户端向服务器发送请求,请求中包含要访问的资源的 URL、请求方法(GET、POST、PUT、DELETE 等)、 请求头(例如,Accept、User-Agent)以及可选的请求体(对于 POST 或 PUT 请求)。
  3. 处理请求:服务器接收到请求后,根据请求中的信息找到相应的资源,执行对应的处理操作。 这可能涉及从数据库中检索数据、生成动态内容或者简单地返回静态文件。
  4. 发送响应:服务器将处理后的结果封装在响应中,并将其发送回客户端。响应包含状态码(用于指示请求的成功或失败)、 响应头(例如,Content-Type、Content-Length)以及可选的响应体(例如,HTML 页面、图像数据)。
  5. 关闭连接:在完成请求-响应周期后,客户端和服务器之间的连接将被关闭, 除非使用了持久连接(如 HTTP/1.1 中的 keep-alive)。

HTTP请求方法

HTTP协议中, GETPOST是两种常用的请求方法, 用于客户端向服务器发送数据和获取资源。

GET 方法

GET方法通常用于从服务器获取资源。它有以下特点

  • 参数传递: 请求参数通过URL中的查询字符串传递,形如?key1=value1&key2=value2。
  • 数据大小限制:由于参数附加在URL后,长度可能受URL长度限制(取决于浏览器和服务器设置)。
  • 安全性: 数据在URL中明文显示,不适合传递敏感信息。

请求格式:

GET <Request-URI> HTTP/<Version>
<Headers>
<Blank Line>
  • Request-URI:表示目标资源的路径,可能包含参数。
  • Version: HTTP协议版本。
  • Headers:包含元信息,例如客户端的属性、支持的格式等。
  • Blank Line:空行。

POST 方法

POST方法通常用于向服务器提交数据。它有以下特点:

  • 参数传递:数据放在请求体中,而不是URL中。
  • 数据大小限制: POST请求的体积没有明显限制,可以传递大量数据。
  • 安全性: 数据在请求体中传输,相对来说更安全。
  • 请求格式:

POST <Request-URI> HTTP/<Version>
<Headers>
<Blank Line>
<Body>
  • Request-URI: 目标资源的路径,通常是API的端点。
  • Headers: 元信息,例如内容类型和长度。
  • Blank Line: 空行,区分头和主体。
  • Body: 数据的主体,包含客户端发送到服务器的长度。

HTTP协议响应内容

HTTP协议响应内容包含状态行、响应头以及响应体三个部分。

状态行

HTTP 状态行包含HTTP协议版本、状态码以及状态描述。

状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型。

状态码分为五类:

  • 1xx(信息性状态码): 表示接收的请求正在处理。
  • 2xx(成功状态码):表示请求正常处理完毕。
  • 3xx(重定向状态码):需要后续操作才能完成这一请求。
  • 4xx(客户端错误状态码):表示请求包含语法错误或无法完成。
  • 5xx(服务器错误状态码): 服务器在处理请求的过程中发生了错误。
示例:

HTTP/1.1 200 OK

响应头

响应头则会包含内容类型、长度、编码等信息。

常见的响应头字段有:

  • Content-Type: 响应内容的MIME类型,例如 text/html、application/json。
  • Content-Length: 响应内容的字节长度。
  • Server: 服务器信息。
  • Set-Cookie: Set-Cookie:

示例:

Content-Type: text/html; charset=UTF-8
Content-Length: 3495
Server: Apache/2.4.41 (Ubuntu)

响应体

响应体包含实际的数据内容,具体形式取决于响应的类型和请求内容。例如:HTML页面内容,JSON数据,文件的二进制数据等。

如果是状态码为204 No Content 或 304 Not Modified的响应,则通常没有正文。

请注意:

响应体和响应头之间会添加一个空行来分隔内容。

Web页面的基本构成

1.HTML(超文本标记语言)

作用:定义网页的结构和内容。

内容

  • 结构标签:如 <html>、<head>、<body>。
  • 内容标签:如 <h1>、<p>、<img>、<a>。
  • 表单标签:如 <form>、<input>、<button>。

2.CSS(层叠样式表)

作用:控制网页的样式和布局。

内容

  • 字体设置:如 font-family、font-size。
  • 颜色设置:如 color、background-color。
  • 布局设计:如 margin、padding、display、flex。
  • 响应式设计:如媒体查询(@media)。

3.JavaScript(脚本语言)

作用:增加网页的交互性和动态功能。

应用:

  • 表单验证。
  • 动画效果。
  • 与服务器交互(如通过 AJAX 请求)。
  • 处理用户事件(如点击、悬停)。

4.Meta 信息

作用:提供页面的元数据,通常包含在 <head> 中。

内容

  • 网页标题:<title>。
  • 字符集:<meta charset="UTF-8>"。
  • SEO 信息:如<meta name="description" content="描述">。
  • 设备适配:如 <meta name="viewport" content="width=device-width, initial-scale=1.0>"。

示例:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Page</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; padding: 20px; }
button { padding: 10px 20px; cursor: pointer; }
</style>
</head>
<body>
<h1>Hello, Web!</h1>
<p>Click the button for a surprise.</p>
<button onclick="alert('You clicked the button!')">Click Me</button>
</body>
</html>
    

Web页面交互

1.HTTP 请求页面

描述::客户端通过 HTTP 协议向服务器发送请求,服务器处理后返回响应。

特点:

  • 最基础的交互方式。
  • 包括常见的 HTTP 方法:GET、POST、PUT、DELETE 等。

示例:

  • GET 请求:浏览器访问网页,获取静态资源(HTML、CSS、JavaScript 等)。
  • POST 请求:提交表单数据。
2.表单提交

描述::通过 HTML 表单向服务器提交数据。

特点:

  • 表单数据会被编码后随请求发送。
  • 可使用 GET 或 POST 方法。

示例:

<form action="/submit" method="post">
<input type="text" name="username" placeholder="Enter your name">
<button type="submit">Submit</button>
</form>
3.AJAX(Asynchronous JavaScript and XML)

描述::使用 JavaScript 在后台与服务器通信,更新部分页面内容而无需刷新整个页面。

特点:

  • 提高用户体验,减少页面加载时间。
  • 现代开发中多用 JSON 代替 XML。

示例:

fetch('/api/data', {
method: 'GET'
})
.then(response => response.json())
.then(data => console.log(data));

Web服务器响应处理

1.直接响应

定义: 服务器直接处理请求,返回静态资源或简单的动态内容,而不调用外部脚本或程序。

特点

  • 高效:直接处理请求,无需额外调用外部程序,适合静态内容。

适用场景:

  • 静态资源(HTML、CSS、JavaScript、图像等)的分发。
  • 轻量级动态内容生成。

工作流程

  1. 客户端发送 HTTP 请求。
  2. 服务器解析请求 URL,查找相应的资源(如文件路径)。
  3. 直接读取资源内容并返回给客户端,附加适当的 HTTP 响应头。

2.CGI 响应

定义: 服务器通过 CGI(Common Gateway Interface)调用外部程序或脚本,处理客户端请求并生成动态响应内容。

特点

  • 灵活性: 可以动态生成内容,支持复杂逻辑。

适用场景:

  • 动态内容生成(如用户登录、数据查询)。
  • 与数据库交互或其他后台服务的复杂逻辑处理。

工作流程

  1. 客户端发送 HTTP 请求。
  2. 服务器解析请求并将请求数据(如 URL 参数或表单数据)传递给 CGI 程序。
  3. CGI 程序处理请求,生成响应内容并返回给服务器。
  4. CGI 程序生成的内容包装为 HTTP 响应发送给客户端。

实现过程

接下来,我们看看如何通过浏览器访问W5500的web页面。

首先需要编写网页内容,这里我们编写了一个简单的网页,如下所示:


#define index_page			"<html>"\
  "<head>"\
  "<title>WIZnet HTTP Server</title>"\
  "<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>"\
  "</head>"\
  "<body>"\
  "<H1 align='center'>WIZnet HTTP Server Examples</H1>"\
  "<H1 align='center'>Welcome!!!!</H1>"\
  "</body>"\
"</html>"

步骤1:注册网页内容


reg_httpServer_webContent((uint8_t *)"index.html", (uint8_t *)index_page);                        // Build HTTP server web pages

reg_httpServer_webContent()函数的作用是将网页的内容注册到库中,这里可以注册网页页面内容,CSS样式内容以及JavaScript脚本内容。具体函数如下:


void reg_httpServer_webContent(uint8_t * content_name, uint8_t * content)
{
  uint16_t name_len;
  uint32_t content_len;

  if(content_name == NULL || content == NULL)
  {
    return;
  }
  else if(total_content_cnt >= MAX_CONTENT_CALLBACK)
  {
    return;
  }

  name_len = strlen((char *)content_name);
  content_len = strlen((char *)content);

  web_content[total_content_cnt].content_name = malloc(name_len+1);
  strcpy((char *)web_content[total_content_cnt].content_name, (const char *)content_name);
  web_content[total_content_cnt].content_len = content_len;
  web_content[total_content_cnt].content = content;

  total_content_cnt++;
}

步骤2:HTTP Server初始化


httpServer_init(http_tx_ethernet_buf, http_rx_ethernet_buf, _WIZCHIP_SOCK_NUM_ - 1, socknumlist); // Initializing the HTTP server

httpServer_init()的作用是初始化HTTP Server参数,四个参数分别是HTTP发送缓存、HTTP接收缓存、使用的SOCKET数量以及对应的SOCKET列表。具体内容如下:


void httpServer_init(uint8_t * tx_buf, uint8_t * rx_buf, uint8_t cnt, uint8_t * socklist)
{
  // User's shared buffer
  pHTTP_TX = tx_buf;
  pHTTP_RX = rx_buf;

  // H/W Socket number mapping
  httpServer_Sockinit(cnt, socklist);
}

步骤3:定时调用HTTP中断函数


/**
* @brief   Hardware Platform Timer Interrupt Callback Function
*/
void TIM2_IRQHandler(void)
{
  static uint32_t wiz_timer_1ms_count = 0;
  if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
  {
    wiz_timer_1ms_count++;
    if (wiz_timer_1ms_count >= 1000)
    {
        httpServer_time_handler();
        DHCP_time_handler();
        wiz_timer_1ms_count = 0;
    }
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
  }
}

步骤4:主循环中调用httpServer_run()函数,运行HTTP Server服务器。


while (1)
{
    int i;
    for (i = 0; i < _WIZCHIP_SOCK_NUM_ - 1; i++)
    {
        httpServer_run(i); // HTTP server test with three-way socket
    }
}

httpServer_run()函数的逻辑跟TCP Server基本一致,也是运行了一个状态机,根据SOCKET不同状态,执行相应的HTTP Server部分的处理,内容如下:


void httpServer_run(uint8_t seqnum)
{
  uint8_t  s; // socket number
  uint16_t len;
  uint32_t gettime = 0;

#ifdef _HTTPSERVER_DEBUG_
  uint8_t destip[4] = {
      0,
  };
  uint16_t destport = 0;
#endif

  http_request        = (st_http_request *)pHTTP_RX; // Structure of HTTP Request
  parsed_http_request = (st_http_request *)pHTTP_TX;

  // Get the H/W socket number
  s = getHTTPSocketNum(seqnum);

  /* HTTP Service Start */
  switch (getSn_SR(s))
  {
  case SOCK_ESTABLISHED:
      // Interrupt clear
      if (getSn_IR(s) & Sn_IR_CON)
      {
          setSn_IR(s, Sn_IR_CON);
      }

      // HTTP Process states
      switch (HTTPSock_Status[seqnum].sock_status)
      {
      case STATE_HTTP_IDLE:
          if ((len = getSn_RX_RSR(s)) > 0)
          {
              if (len > DATA_BUF_SIZE) len = DATA_BUF_SIZE;
              len = recv(s, (uint8_t *)http_request, len);

              *(((uint8_t *)http_request) + len) = '\0';

              parse_http_request(parsed_http_request, (uint8_t *)http_request);
#ifdef _HTTPSERVER_DEBUG_
              getSn_DIPR(s, destip);
              destport = getSn_DPORT(s);
              printf("\r\n");
              printf("> HTTPSocket[%d] : HTTP Request received ", s);
              printf("from %d.%d.%d.%d : %d\r\n", destip[0], destip[1], destip[2], destip[3], destport);
#endif
#ifdef _HTTPSERVER_DEBUG_
              printf("> HTTPSocket[%d] : [State] STATE_HTTP_REQ_DONE\r\n", s);
#endif
              // HTTP 'response' handler; includes send_http_response_header / body function
              http_process_handler(s, parsed_http_request);

              gettime = get_httpServer_timecount();
              // Check the TX socket buffer for End of HTTP response sends
              while (getSn_TX_FSR(s) != (getSn_TxMAX(s)))
              {
                  if ((get_httpServer_timecount() - gettime) > 3)
                  {
#ifdef _HTTPSERVER_DEBUG_
                      printf("> HTTPSocket[%d] : [State] STATE_HTTP_REQ_DONE: TX Buffer clear timeout\r\n", s);
#endif
                      break;
                  }
              }

              if (HTTPSock_Status[seqnum].file_len > 0)
                  HTTPSock_Status[seqnum].sock_status = STATE_HTTP_RES_INPROC;
              else
                  HTTPSock_Status[seqnum].sock_status = STATE_HTTP_RES_DONE; // Send the 'HTTP response' end
          }
          break;

      case STATE_HTTP_RES_INPROC:
          /* Repeat: Send the remain parts of HTTP responses */
#ifdef _HTTPSERVER_DEBUG_
          printf("> HTTPSocket[%d] : [State] STATE_HTTP_RES_INPROC\r\n", s);
#endif
          // Repeatedly send remaining data to client
          send_http_response_body(s, 0, http_response, 0, 0);

          if (HTTPSock_Status[seqnum].file_len == 0) HTTPSock_Status[seqnum].sock_status = STATE_HTTP_RES_DONE;
          break;

      case STATE_HTTP_RES_DONE:
#ifdef _HTTPSERVER_DEBUG_
          printf("> HTTPSocket[%d] : [State] STATE_HTTP_RES_DONE\r\n", s);
#endif
          // Socket file info structure re-initialize
          HTTPSock_Status[seqnum].file_len    = 0;
          HTTPSock_Status[seqnum].file_offset = 0;
          HTTPSock_Status[seqnum].file_start  = 0;
          HTTPSock_Status[seqnum].sock_status = STATE_HTTP_IDLE;

//#ifdef _USE_SDCARD_
//					f_close(&fs);
//#endif
#ifdef _USE_WATCHDOG_
          HTTPServer_WDT_Reset();
#endif
          http_disconnect(s);
          break;

      default:
          break;
      }
      break;

  case SOCK_CLOSE_WAIT:
#ifdef _HTTPSERVER_DEBUG_
      printf("> HTTPSocket[%d] : ClOSE_WAIT\r\n", s); // if a peer requests to close the current connection
#endif
      disconnect(s);
      break;

  case SOCK_CLOSED:
      if (reboot_flag)
      {
          NVIC_SystemReset();
      }
#ifdef _HTTPSERVER_DEBUG_
      printf("> HTTPSocket[%d] : CLOSED\r\n", s);
#endif
      if (socket(s, Sn_MR_TCP, HTTP_SERVER_PORT, 0x00) == s) /* Reinitialize the socket */
      {
#ifdef _HTTPSERVER_DEBUG_
          printf("> HTTPSocket[%d] : OPEN\r\n", s);
#endif
      }
      break;

  case SOCK_INIT:
      listen(s);
      break;

  case SOCK_LISTEN:
      break;

  default:
      break;

  } // end of switch

#ifdef _USE_WATCHDOG_
  HTTPServer_WDT_Reset();
#endif
}

运行结果

请注意:

测试实例需要PC端和W5500处于同一网段。

烧录例程运行后,首先进行了PHY链路检测,然后是通过DHCP获取网络地址并打印网络地址信息, 最后HTTP Server程序开始监听客户端的请求并处理响应,如下图所示:

接着我们打开浏览器,输入W5500的IP地址进行访问。

总结

本文介绍了在 W5500 芯片上实现 HTTP Server 功能,阐述了 HTTP 协议的概念、特点、应用场景、工作流程、请求方法、响应内容,以及 Web 页面构成和交互方式。展示了在W5500上实现的过程。

下一篇将讲解在该芯片上实现 SNTP 授时功能,介绍从 SNTP 服务器获取准确时间的原理和实现步骤。敬请期待!

下载本章例程

我们提供完整的工程文件以及配套开发板,方便你随时测试,快速完成产品开发:

开发环境: Keil MDK5 配套开发板