你人生中的第一个‘‘http‘‘服务器-详解tinnyhttp和修改函数


前言:
本篇文章需要你要两个前置知识
第一 你需要了解网络编程基础知识 和socket、bind、listen和accept函数
第二 你需要了解什么是http协议
参考
1.网络编程
2.http协议


1.构建连接

  • 初始化
startup(u_short *port)
{
    
    
 int httpd = 0;
 struct sockaddr_in name;

 httpd = socket(PF_INET, SOCK_STREAM, 0);
 if (httpd == -1)
  error_die("socket");
 memset(&name, 0, sizeof(name));
 name.sin_family = AF_INET;
 name.sin_port = htons(*port);
 name.sin_addr.s_addr = htonl(INADDR_ANY);
 if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
  error_die("bind");
 if (*port == 0)  /* if dynamically allocating a port */
 {
    
    
  int namelen = sizeof(name);
  if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
   error_die("getsockname");
  *port = ntohs(name.sin_port);
  )

解析 连接三部曲
1.socket初始化
2.地址族初始化
3.bind和listen的初始化
当你学到了高级语言的时候,一般会封装成了一个类.
补充
getsockname:这是获取一个socket细节的一个函数,假如绑定失败,就会返回-1

  • 连接器
client_sock = accept(server_sock,
                       (struct sockaddr *)&client_name,
                       &client_name_len);
  if (client_sock == -1)
   error_die("accept");

accept函数作为一个连接器,可以连接客户端,成功返回连接的连接短的socket_fd;

2.HTTP

服务端主要是处理请求的

  • 处理请求行的方法
{
    
    
 char buf[1024];
 int numchars;
 char method[255];
 char url[255];
 char path[512];
 size_t i, j;
 struct stat st;
 int cgi = 0;      /* becomes true if server decides this is a CGI
                    * program */
 char *query_string = NULL;

 numchars = get_line(client, buf, sizeof(buf));
 i = 0; j = 0;
 while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
 {
    
    
  method[i] = buf[j];
  i++; j++;
 }
 method[i] = '\0';

 if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
 {
    
    
  unimplemented(client);
  return;
 }

 if (strcasecmp(method, "POST") == 0)
  cgi = 1;

首先 提起方法 是get还是post,这个服务器简陋,所以只实现了get和post方法

  • 处理请求行的url
 i = 0;
 while (ISspace(buf[j]) && (j < sizeof(buf)))
  j++;
 while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
 {
    
    
  url[i] = buf[j];
  i++; j++;
 }
 url[i] = '\0';
 if (strcasecmp(method, "GET") == 0)
 {
    
    
  query_string = url;
  while ((*query_string != '?') && (*query_string != '\0'))
   query_string++;
  if (*query_string == '?')
  {
    
    
   cgi = 1;
   *query_string = '\0';
   query_string++;
  }
 }

这里讲url分割成两个部分 一个是url的路径,一个是url的查询符串
比如说一个url
http://example.com/path?param1=value1&param2=value2
经过上述代码处理后,
url 就变为 "http://example.com/path"。
query_string 指向 "param1=value1&param2=value2"。

**补充知识 **

C语言当中数组 的结束是\0,url 强行在的位置结束了
query_string++就继续继承url的查询部分

  • 处理url
sprintf(path, "htdocs%s", url);
 if (path[strlen(path) - 1] == '/')
  strcat(path, "index.html");

将你的输入的url定位到网址首页(通俗理解)

响应部分

错误处理

void not_found(int client)
{
    
    
 char buf[1024];

 sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
 send(client, buf, strlen(buf), 0);
 sprintf(buf, SERVER_STRING);
 send(client, buf, strlen(buf), 0);
 sprintf(buf, "Content-Type: text/html\r\n");
 send(client, buf, strlen(buf), 0);
 sprintf(buf, "\r\n");
 send(client, buf, strlen(buf), 0);
 sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");
 send(client, buf, strlen(buf), 0);
 sprintf(buf, "<BODY><P>The server could not fulfill\r\n");
 send(client, buf, strlen(buf), 0);
 sprintf(buf, "your request because the resource specified\r\n");
 send(client, buf, strlen(buf), 0);
 sprintf(buf, "is unavailable or nonexistent.\r\n");
 send(client, buf, strlen(buf), 0);
 sprintf(buf, "</BODY></HTML>\r\n");
 send(client, buf, strlen(buf), 0);
}

这个部分包含响应全部部分

  • 响应行
sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
 send(client, buf, strlen(buf), 0);
  • 响应头
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), 0);
    • SERVER_STRING 一般包含服务器的相关信息,例如服务器的名称和版本,这属于响应头的一部分。
    • Content-Type: text/html\r\n 明确了响应体的内容类型是 HTML。
    • 空行 \r\n 标志着响应头的结束。
  • 响应体
sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<BODY><P>The server could not fulfill\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "your request because the resource specified\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "is unavailable or nonexistent.\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "</BODY></HTML>\r\n");
send(client, buf, strlen(buf), 0);

展现了些代码片段组合起来形成了一个 HTML 格式的响应体,用于向客户端展示 404 错误的相关信息。

静态页面的响应

void serve_file(int client, const char *filename)
{
    
    
 FILE *resource = NULL;
 int numchars = 1;
 char buf[1024];

 buf[0] = 'A'; buf[1] = '\0';
 while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
  numchars = get_line(client, buf, sizeof(buf));

 resource = fopen(filename, "r");
 if (resource == NULL)
  not_found(client);
 else
 {
    
    
  headers(client, filename);
  cat(client, resource);
 }
 fclose(resource);
}

这个首先是处理请求,丢失了所以的请求,简化了逻辑,(只访问页面,(知道路径就好))

  • 响应头和响应行
void headers(int client, const char *filename)
{
    
    
 char buf[1024];
 (void)filename;  /* could use filename to determine file type */

 strcpy(buf, "HTTP/1.0 200 OK\r\n");
 send(client, buf, strlen(buf), 0);
 strcpy(buf, SERVER_STRING);
 send(client, buf, strlen(buf), 0);
 sprintf(buf, "Content-Type: text/html\r\n");
 send(client, buf, strlen(buf), 0);
 strcpy(buf, "\r\n");
 send(client, buf, strlen(buf), 0);
}

和上面错误处理一样 不过多论述了

  • 响应体
void cat(int client, FILE *resource)
{
    
    
 char buf[1024];

 fgets(buf, sizeof(buf), resource);
 while (!feof(resource))
 {
    
    
  send(client, buf, strlen(buf), 0);
  fgets(buf, sizeof(buf), resource);
 }
}

发送一个html给客户端

补充知识

浏览器具有内置的渲染引擎,能够识别 HTML 标签、CSS 样式和 JavaScript 脚本等,并根据相关的标准和规范将其转换为用户可见的网页内容。只要发送的 HTML 文件格式正确且完整,浏览器就能对其进行处理并展示出相应的页面结构、文本、图像、链接等元素。

动态页面的响应

void execute_cgi(int client, const char *path,
                 const char *method, const char *query_string)
{
    
    
 char buf[1024];
 int cgi_output[2];
 int cgi_input[2];
 pid_t pid;
 int status;
 int i;
 char c;
 int numchars = 1;
 int content_length = -1;

 buf[0] = 'A'; buf[1] = '\0';
 if (strcasecmp(method, "GET") == 0)
  while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
   numchars = get_line(client, buf, sizeof(buf));
 else    /* POST */
 {
    
    
  numchars = get_line(client, buf, sizeof(buf));
  while ((numchars > 0) && strcmp("\n", buf))
  {
    
    
   buf[15] = '\0';
   if (strcasecmp(buf, "Content-Length:") == 0)
    content_length = atoi(&(buf[16]));
   numchars = get_line(client, buf, sizeof(buf));
  }
  if (content_length == -1) {
    
    
   bad_request(client);
   return;

对于get方法 也是丢失了请求,
对于POST 方法

if (strcasecmp(buf, “Content-Length:”) == 0):
比较截取后的 buf 是否等于 “Content-Length:”,如果相等,说明当前行是 Content-Length 请求头字段。
content_length = atoi(&(buf[16]));:
如果是 Content-Length 字段,使用 atoi 函数将该字段后面的字符串转换为整数,赋值给 content_length 变量,这个变量表示 POST 请求体的长度。

还有一部分是cgi的响应,无需了解。(它进程开销优点大)
如果你想了解 建议阅读网络知识,先了解进程和pipe函数