HTTP协议详解

HTTP协议

HTTP即超文本传输协议(HyperText Transfer Protocol),是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。

2. 请求信息——Request
客户端发送一个HTTP请求到服务器,请求消息格式为如下四部分组成的一个字符串。

请求行(request line)
请求头(header)
空行
请求体


第一行请求行指定<方法>、<URL>、<协议版本>以空格分隔,如下例

GET /hello HTTP/1.1
1
第二行起为请求头部,Host指出请求的目的地(主机域名);User-Agent是客户端的信息,它是检测浏览器类型的重要信息,由浏览器定义,并且在每个请求中自动发送,如下实例

GET /hello HTTP/1.1

第二行起为请求头部,Host指出请求的目的地(主机域名);User-Agent是客户端的信息,它是检测浏览器类型的重要信息,由浏览器定义,并且在每个请求中自动发送,如下实例 

Host: www.google.com
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6

第三行为空行,放入\r\n字符

第四行为请求体,可以添加任意的其它数据,一般POST请求使用

3. 响应信息——Response
服务器收到客户端的请求后,就会有一个HTTP的响应消息,HTTP响应也由四部分组成

状态行
状态行由协议版本号状态码、状态消息组成
响应头
响应头是客户端可以使用的一些信息,如:Date(生成响应的日期)、Content-Type(MIME类型及编码格式)、Connection(默认是长连接)等等
空行
第三行是空行
响应体
响应正文,服务器返回给客户端的文本信息

Response

HTTP/1.1 200 OK 
Date: Fri, 22 May 2009 06:07:21 GMT
Content-Type: text/html; charset=UTF-8

<html><head>Hello</head> <body>Hello,world </body> </html>

4. 请求方法
即请求行中指定的请求方法。HTTP/1.1协议中共定义了八种方法(也叫“动作”)来以不同方式操作指定的资源。最常用的请求方法是GET和POST

请求方法    简述
GET    请求指定的页面信息,并返回实体主体,由于服务器的限制,对get提交通常有长度限制
POST    用来传输实体的主体。虽然get也可以,但是一般是用post传输。在REST架构标准的网站中,post被用来创建资源
PUT    用来传输文件。就像FTP协议的文件上传一样,要求在请求报文的主体中包含文件内容,然后保存到请求URI指定的位置中。在REST架构标准的网站中,会开放此方法用来更新资源。
DELETE    用来删除文件,是与put相反的方法。REST架构标准的网站中,会开放此方法用来作为删除资源。
HEAD    和get方法一样,只是不返回报文主体部分。用于确认uri的有效性及资源更新的时间等
TRACE    回显服务器收到的请求,主要用于测试或诊断
CONNECT    HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。通常用于SSL加密服务器的链接
OPTIONS    可使服务器传回该资源所支持的所有HTTP请求方法。用’*'来代替资源名称,向Web服务器发送OPTIONS请求,可以测试服务器功能是否正常运作

 

4.1 GET和POST的区别
GET提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连,如EditPosts.aspx?name=test1&id=123456。 POST方法是把提交的数据放在HTTP包的Body中。因此GET提交的数据会在地址栏中显示出来,而POST提交,地址栏不会改变

GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制。

GET方式需要使用Request.QueryString来取得变量的值,而POST方式通过Request.Form来获取变量的值。

GET方式提交数据,会带来安全问题,比如一个登录页面,通过GET方式提交数据时,用户名和密码将出现在URL上,如果页面可以被缓存或者其他人可以访问这台机器,就可以从历史记录获得该用户的账号和密码。

5. 状态码
在响应信息的状态行中。状态码以3位数字组成,状态代码的第一个数字代表当前响应的类型

1xx消息——请求已被服务器接收,继续处理
2xx成功——请求已成功被服务器接收、理解、并接受
3xx重定向——需要后续操作才能完成这一请求
4xx请求错误——请求含有词法错误或者无法被执行
5xx服务器错误——服务器在处理某个正确请求时发生错误
 

200 OK                      //客户端请求成功
400 Bad Request             //客户端请求有语法错误,不能被服务器所理解
401 Unauthorized            //请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用 
403 Forbidden               //服务器收到请求,但是拒绝提供服务
404 Not Found               //请求资源不存在,eg:输入了错误的URL
500 Internal Server Error   //服务器发生不可预期的错误
503 Server Unavailable      //服务器当前不能处理客户端的请求,一段时间后可能恢复正常

å¨è¿éæå¥å¾çæè¿°

使用C实现简单HTTP服务器

可以看到,数据并不是一起发送,而是一条一条的发送。

void not_found(Client client){                                 /* 返回404 */
    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);
}

void bad_request(Client client){                              /* 发送400 */
    char buf[1024];
    sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");
    send(client, buf, sizeof(buf), 0);
    sprintf(buf, "Content-type: text/html\r\n");
    send(client, buf, sizeof(buf), 0);
    sprintf(buf, "\r\n");
    send(client, buf, sizeof(buf), 0);
    sprintf(buf, "<P>Your browser sent a bad request, ");
    send(client, buf, sizeof(buf), 0);
    sprintf(buf, "such as a POST without a Content-Length.\r\n");
    send(client, buf, sizeof(buf), 0);
}
void headers(Client client, const char *filename){            /* 200发送HTTP头 */
    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);
}

501 方法

void unimplemented(Client client){                              /* 501 相应方法未实现 */
    char buf[1024];
    sprintf(buf, "HTTP/1.0 501 Method Not Implemented\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><HEAD><TITLE>Method Not Implemented\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "</TITLE></HEAD>\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");
    send(client, buf, strlen(buf), 0);
    sprintf(buf, "</BODY></HTML>\r\n");
    send(client, buf, strlen(buf), 0);
}

 

打开一个文件发送

void cat(Client client, FILE *resource){
    char buf[1024];
    fgets(buf, sizeof(buf), resource);                      /* 读取文件到buf中 */
    while (!feof(resource)){                                /* 判断文件是否读取到末尾 */
        send(client, buf, strlen(buf), 0);                  /* 读取并发送文件内容 */
        fgets(buf, sizeof(buf), resource);
    }
}

读取http请求行,http请求行以\r\n结尾,所以必须用特殊的方法读取,并去掉结尾的\r\n

int get_line(Client client, char *buf, int size){
    int i = 0;
    char c = '\0';
    while ((i < size - 1) && (c != '\n')){
        if (recv(client, &c, 1, 0) > 0) {
            if (c == '\r') {
                if ((recv(client, &c, 1, MSG_PEEK) > 0) && (c == '\n'))
                    recv(client, &c, 1, 0);
                else c = '\n';
            }
            buf[i] = c;
            i++;
        }else c = '\n';
    }
    buf[i] = '\0';
    return(i);
}

接收并处理http请求

读取HTTP的请求行,根据

/* 接收客户端的连接,读取请求数据 */
void *accept_request(void* args){
    Client client = (Client)args;
    char *method = NULL;
    char *url = NULL;
    char buf[BUF_SIZE];
    int numchars;
    char path[512];
    struct stat st;
    int cgi = 0;                 
    char *query_string = NULL;

    memset(buf, 0, sizeof(buf));
    /* 获取一行HTTP报文数据 */
    if ((numchars = get_line(client, buf, sizeof(buf))) == 0) return NULL;

    /* 获取Http请求行字段,格式为<method> <request-URL> <version> 每个字段以空白字符相连 */
    method = strtok(buf, " ");                                /* 从请求行中分割出method字段 */

    /* 本Demo仅实现GET请求和POST请求 */
    if (StrCaseCmp(method, "GET") && StrCaseCmp(method, "POST")){
        unimplemented(client);
        return NULL;
    }

    /* 如果请求方法为POST,cgi标志位置1,开启cgi解析 */
    if (StrCaseCmp(method, "POST") == 0) cgi = 1;

    url = strtok(NULL, " ");              /* 从请求行中分割出request-URL字段 */
    
    if (StrCaseCmp(method, "GET") == 0){  /* GET请求,url可能带有"?",有查询参数 */
        query_string = url;
        while ((*query_string != '?') && (*query_string != '\0'))
            query_string++;
        if (*query_string == '?'){
            cgi = 1;                       /* 如果带有查询参数,执行cgi解析参数,设置标志位为1 */
            *query_string = '\0';          /* 将解析参数截取下来 */
            query_string++;
        }
    }

    /* url中的路径格式化到 path */
    sprintf(path, "static%s", url);

    /* 如果path只是一个目录,默认设置为首页 */
    if (path[strlen(path) - 1] == '/') strcat(path, "index.html");

    if (stat(path, &st) == -1) {     /* 访问的网页不存在,则读取剩下的请求头信息并丢弃 */
        while ((numchars > 0) && strcmp("\n", buf))  
            numchars = get_line(client, buf, sizeof(buf));
        not_found(client); 
    }else{
        /* 网页存在。路径如果是目录,显示主页 (S_IFDIR代表目录) */
        if ((st.st_mode & S_IFMT) == S_IFDIR) strcat(path, "/index.html");
#ifdef _ZJ_WIN32
        char *suffix = &path[strlen(path) - 4];
        if (StrCaseCmp(suffix, ".cgi") == 0) cgi = 1;
#else
        if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH))
            cgi = 1;
#endif // _ZJ_WIN32
        if (!cgi) serve_file(client, path);                     /* 将静态文件返回 */
        else execute_cgi(client, path, method, query_string);   /* 执行cgi动态解析 */
    }
    CloseSocket(client);                                        /* 关闭套接字 */
    return NULL;
}

 

读取请求行,常常涉及split的操作,对于C语言库函数strtok函数,这个函数用法比较特殊

头文件:<string.h>
函数原型:char * strtok(char *s, const char *delim);
参数s 指向将要分割的字符串,参数delim 为分割符,即以之做为分割的标志。当函数在参数s 的字符串中发现参数delim 符时则会将该字符改为\0 字符。在第一次调用时,strtok必需给予参数s 字符串,往后的调用则将参数s 设置成NULL。每次调用成功则返回下一个分割后的字符串指针。
返回值:返回下一个分割后的字符串指针,如果已无则返回NULL
#include <stdio.h>
#include <string.h>

int main(){
    char str[] = "python Golang C++ Java JavaScript";
    /* 以空格作为分割符分割字符串 */
    char *p = strtok(str, " ");
    printf("%s\n", p);
    while((p = strtok(NULL, " ")))
        printf("%s\n",p);

    return 0;
}

 

 

Web基础

1. CGI 协议

CGIæå¡å¨

CGI规范允许Web服务器执行外部程序,并将它们的输出发送给Web浏览器,CGI将Web的一组简单的静态超媒体文档变成一个完整的新的交互式媒体。通俗的讲CGI就像是一座桥,把网页和WEB服务器中的执行程序连接起来,它把HTML接收的指令传递给服务器的执行程序,再把服务器执行程序的结果返还给HTML页。CGI 的跨平台性能极佳,几乎可以在任何操作系统上实现。

实际上有多种方式可以执行CGI程序,但对http的请求方法来说,只有get和post两种方法允许执行CGI脚本(实际上post方法的内部本质还是get方法,只不过在发送http请求时,get和post方法对url中的参数处理方式不一样而已)。

常用于编写CGI的语言有perl、php、python等,实际上任何一种语言都能编写CGI,java也一样能写,但java的servlet完全能实现CGI的功能,且更优化、更利于开发。
 

1.1 特点
CGI方式在遇到连接请求(用户请求)时先要创建CGI的子进程,激活一个CGI进程,然后处理请求,处理完后结束这个子进程。所以用CGI方式的服务器有多少连接请求就会有多少CGI子进程,子进程反复加载是CGI性能低下的主要原因。当用户请求数量非常多时,会大量挤占系统的资源如内存,CPU时间等,造成效能低下。

1.2 CGI脚本工作流程
浏览器通过HTML表单或超链接请求指向一个CGI应用程序的URL
服务器收发到请求
服务器执行所指定的CGI应用程序
CGI应用程序执行所需要的操作,通常是基于浏览者输入的内容
CGI应用程序把结果格式化为网络服务器和浏览器能够理解的文档(通常为HTML网页)
网络服务器把结果返回到浏览器中
 

1.3 实现原理
一般情况下,服务器和CGI程序之间是通过标准输入输出来进行数据传递的,而这个过程需要环境变量的协作方可实现。每个CGI程序只能处理一个用户请求,所以在激活一个CGI程序进程时也创建了属于该进程的环境变量。

1.服务器将URL指向一个CGI应用程序
2.服务器为应用程序执行做准备
3.应用程序执行,读取标准输入和有关环境变量
4.应用程序进行标准输出
 

å¨è¿éæå¥å¾çæè¿°

1.3.1 CGI 接口标准
接口标准    简述
标准输入    CGI程序像其他可执行程序一样,可通过标准输入(stdin)从Web服务器得到输入信息,如Form中的数据,这就是所谓的向CGI程序传递数据的POST方法。这意味着在操作系统命令行状态可执行CGI程序,对CGI程序进行调试。POST方法是常用的方法。
环境变量    操作系统提供了许多环境变量,它们定义了程序的执行环境,应用程序可以存取它们。Web服务器和CGI接口又另外设置了自己的一些环境变量,用来向CGI程序传递一些重要的参数。CGI的GET方法还通过环境变量QUERY_STRING向CGI程序传递Form中的数据。
标准输出    CGI程序通过标准输出(stdout)将输出信息传送给Web服务器。传送给Web服务器的信息可以用多种格式,通常是以纯文本或者HTML文本的形式,这样我们就可以在命令行状态调试CGI程序,并且得到它们的输出。
对于CGI程序来说,它继承了系统的环境变量。CGI的环境变量在CGI程序启动时初始化,在结束时销毁。当一个CGI程序不是被HTTP服务器调用时,它的环境变量几乎是系统环境变量的复制,而当这个CGI程序被HTTP服务器调用时,它的环境变量就会多出以下关于HTTP服务器、客户端、CGI传输过程等内容

 

GET    通过在URL中嵌入的形式传递参数。对CGI程序而言,在GET请求中传递的参数要通过环境变量“QUERY_STRING”来接收。    1、参数的内容作为URL信息,用户可以看到;2、有大小的限制。
POST    CGI程序从标准输入接收参数。与GET方法不同的是,参数的内容从URL信息中不能获得,对于大小也没有限制。    与GET方法问题1、2完全相反
 

实现一个cgi程序

//sayhi.c
#include <stdio.h>

int main(){
	printf("Content-Type: text/html\n");
	printf("\n");
    printf("<html>");
    printf("<head>");
    printf("<title>CGI</title>");
    printf("</head>");
    printf("<body>");
	printf("I am a CGI program!\n");
    printf("</body>");
    printf("</html>\n");
	
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值