HTTP协议

前言

超级详细的HTTP详解

CGI详解

1.HTTP介绍

HTTP是一个简单的协议。客户进程建立一条同服务器进程的TCP连接,然后发出请求并读取服务器进程的响应。服务器进程关闭连接表示本次响应结束。服务器进程返回的文件通常含有指向其他服务器上文件的指针(超文本链接)。用户显然可以很轻松地沿着这些链接从一个服务器到下一个服务器。HTTP是一个应用层协议,由请求和响应构成,是一个标准的客户端服务器模型。HTTP是一个无状态的协议。HTTP协议通常承载于TCP协议之上,有时也承载于TLS或SSL协议层之上,这个时候,就成了我们常说的HTTPS。如下图所示:


默认HTTP的端口号为80,HTTPS的端口号为443。

2.HTTP的过程:

1. 当你在浏览器输入URL http://www.cnblogs.com 的时候,浏览器发送一个Request去获取 http://www.cnblogs.com 的html.  服务器把Response发送回给浏览器.
2. 浏览器分析Response中的 HTML,发现其中引用了很多其他文件,比如图片,CSS文件,JS文件。
3. 浏览器会自动再次发送Request去获取图片,CSS文件,或者JS文件。
4. 等所有的文件都下载成功后。 网页就被显示出来了。

3.报文的结构:

HTTP/1.0报文有两种类型:请求和响应。 

HTTP/1.0请求的格式是:

request-line
headers ( 0或有多个)
<blank line>
body (只对POST请求有效)
request - line的格式是:
request request-URI HTTP版本号
支持以下三种请求:
1)GET请求,返回request-URI所指出的任意信息。
2)HEAD请求,类似于GET请求,但服务器程序只返回指定文档的首部信息,而不包含实际的文档内容。该请求通常被用来测试超文本链接的正确性、可访问性和最近的修改。

3)POST请求用来发送电子邮件、新闻或发送能由交互用户填写的表格。这是唯一需要在请求中发送body的请求。使用POST请求时需要在报文首部 Content - Length字段中指出body的长度。对一个繁忙的 We b服务器进行采样,统计结果表明:500 000个客户程序的请求中有132计计第二部分 TCP的其他应用 下载9 9 . 6 8 %是GET请求,0 . 2 5 %是HEAD请求,0 . 0 7 %是POST请求。

我们看看GET和POST的区别
1. GET提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连,如EditPosts.aspx?name=test1&id=123456.  POST方法是把提交的数据放在HTTP包的Body中.
2. GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制.
3. GET方式需要使用Request.QueryString来取得变量的值,而POST方式通过Request.Form来获取变量的值。
4. GET方式提交数据,会带来安全问题,比如一个登录页面,通过GET方式提交数据时,用户名和密码将出现在URL上,如果页面可以被缓存或者其他人可以访问这台机器,就可以从历史记录获得该用户的账号和密码.

举个例子:

HTTP请求:

GET / HTTP/1.1
Host: www.cnblogs.com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:49.0) Gecko/20100101 Firefox/49.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Connection: keep-alive
这是一个真实的HTTP请求的例子,其中每一行都以\r\n结尾。由于我们写的是简单的服务器,所以我们只关心其中的几行。
  • 第一行称为请求行,GET是请求方法,表示获取资源,除此之外还有POST方法、PUT方法、HEAD方法、DELETE方法和OPTIONS方法等。由于我们写一个简单的服务器,所以暂时仅支持GET方法。/是URI,表示客户希望访问的资源的URI。HTTP/1.1是HTTP协议的版本,此例中表示1.1版本。我们需要解析请求行,需要解析出方法字段、URI和HTTP协议版本。
  • 第二行是Host字段,表示所请求的资源所在的主机名和端口号。
  • 第三行User-Agent是客户的浏览器的类型,此例是运行在Ubuntu上的Firefox浏览器。
  • 第四行Accept表示客户接受的资源的类型。
  • 第五行Accept-Language表示客户接受的语言类型。
  • 第六行Connection表示服务器在发送完客户请求的数据之后是否断开TCP连接。keep-alive表示不断开,close表示断开。

HTTP应答:

HTTP/1.1 200 OK
Server: Apache/2.2.22 (Debian)
Content-length: 1223
Content-Type: text/html
  • 第一行为应答行,HTTP/1.1是协议版本,200是状态码,OK是状态短语,表示请求正常。
  • 第二行Server表示服务器的类型,此例中是Apache服务器。
  • 第三行Content-length表示实体的长度,单位字节。
  • 第四行Content-Type表示实体的文件类型。

4.代码实现

servnet.c

/* 源程序 servnet.c */
#include<stdio.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
#include<fcntl.h>
#include<sys/types.h>
unsigned short port=80 ;
char*error_return="<HTML>\n<BODY>File not found\n</BODY>\n</HTML>" ;
//当所请求的 HTML 文件不存在时返回该信息给浏览器
char ret_buf[32768];
char*envp[10];
//环境变量
char pathname[100];
//CGI 程序路径名
char cginame[20];
//CGI 程序名
char para[1024];
//CGI 参数
char*read_file(char*buf,int num_buf);
//该函数的功能是从 buf 中分离出绝对路径名,然后检验文件的合法性,然后读
//取文件中的数据,长度由 num_buf 决定,然后返回所独取得数据。
char*getcontentlen(char*buf);
//该函数的功能是从 buf 中读取 CGI 参数的长度并将之写入环境变量中。
20 
char*getcontenttype(char*buf);
//该函数的功能是从 buf 中读取内容类型并将之写入环境变量中。
char*getpathname(char*buf,char*request);
//该函数的功能是从 buf 中读取 CGI 程序路径名,并将之存入相应的变量。
char*getcginame(char*pathname);
//该函数的功能是从 buf 中读取 CGI 程序名,并将之存入相应的变量。
char*getparaments(char*buf);
//该函数的功能是从 buf 中读取 CGI 参数,并将之存入相应的变量。
int main(int argc,char*argv[])
{
    int i,acc_sock ;
    //acc_sock 为连接套接子
    char*recvBuffer=(char*)malloc(4001);
    //服务器端接收缓冲区
    int rc=0 ;
    int serverSocket ;
    struct sockaddr_in serverAddr ;
    struct sockaddr_in clientAddr ;
    int clientAddrSize ;
    struct hostent*entity ;
    int totalReceived ;
    int size ;
    int totalSent ;
    int bytesSent ;
    int pid2 ;
    int fd1[2],fd2[2];
    //pipe: fd[0] read,fd[1] write
    char*cbuf ;
    char request[5];
    //设置环境变量的初始值
    envp[0]=(char*)malloc(sizeof(char)*50);
    
    strcpy(envp[0],"CONTENT_TYPE=application/x-www-form-urlencoded");
    envp[1]=(char*)malloc(sizeof(char)*30);
    strcpy(envp[1],"REQUEST_METHOD=POST");
    envp[2]=(char*)malloc(sizeof(char)*30);
    strcpy(envp[2],"CONTENT_LENGTH=38");
    envp[3]=(char*)malloc(sizeof(char)*1024);
    strcpy(envp[3],"QUERY_STRING=");
    printf("initial over...........\n");
    serverSocket=socket(AF_INET,SOCK_STREAM,0);
    //建立 socket
    if(serverSocket==-1)
    {
        printf("Invalid socket\n");
        exit(1);
    }
    serverAddr.sin_family=AF_INET ;
    serverAddr.sin_port=htons(port);
    serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
    memset(&(serverAddr.sin_zero),0,8);
    printf("Binding server socket to port %d\n",port);
    rc=bind(serverSocket,(struct sockaddr*)&serverAddr,sizeof(struct 
    sockaddr));
    //绑定
    if(rc==-1)
    {
        printf("Bad bind\n");
        exit(1);
    }
    rc=listen(serverSocket,10);
    //允许 10 个连接
    if(rc==-1)
    {
        printf("Bad listen\n");
        exit(1);
    }
    while(1)
    {
        printf("\nAccepting connections...\n");
        clientAddrSize=sizeof(struct sockaddr_in);
        //阻塞方式等待客户连接
        acc_sock=accept(serverSocket,(struct 
        sockaddr*)&clientAddr,&clientAddrSize);
        if(fork()==0)
        {
            if(acc_sock==-1)
            {
                printf("Bad accept\n");
                exit(1);
            }
            
        }
        entity=gethostbyaddr((char 
        *)&clientAddr.sin_addr,sizeof(struct in_addr),AF_INET);
        printf("Connection  from   .........%s\n",inet_ntoa((struct 
        in_addr)clientAddr.sin_addr));
        i=recv(acc_sock,recvBuffer,4000,0);
        if(i==-1)
        {
            printf("recv error!\n");
            exit(0);
        }
        recvBuffer[i]='\0' ;
        printf("Received from client........%s\n",recvBuffer);
        printf("*****************recvbuf                     display  
        end**********************\ n ");  
        /
        //本服务器所有的 cgi 程序都放在一个 cgi-bin 文件夹中,因此通过判断接收缓
        //冲区中的字符时不是包含 cgi-bin 来判断是否为 cgi 请求
        if(strstr(recvBuffer,"cgi-bin")==NULL)
        {
            printf("$$this is not a CGI REQUEST!!\n");
            //如果不是 cgi 请求,那么直接调用函数 read_file(),返回所请求的文件
            cbuf=read_file(recvBuffer,totalReceived);
            size=strlen(cbuf);
            totalSent=0 ;
            do 
            {
                bytesSent=send(acc_sock,cbuf+totalSent,i=strlen(cbuf+totalSent),0);
                if(bytesSent==-1)break ;
                totalSent+=bytesSent ;
            }
            while(totalSent<size);
            close(acc_sock);
        }
        else 
        {
            printf("$$this is a CGI REQUEST!!\n");
            getparaments(recvBuffer);
            
            getcontentlen(recvBuffer);
            //
            getcontenttype(recvBuffer);
            //
            request[4]='\0' ;
            printf("step1\n");
            strncpy(request,recvBuffer,4);
            printf("request :|%s|\n",request);
            getpathname(recvBuffer,request);
            getcginame(pathname);
            if(pipe(fd1)<0||pipe(fd2)<0)
            printf("pipe error\n");
            if((pid2=fork())<0)
            printf("fork error\n");
            if(pid2>0)
            {
                close(fd1[0]);
                close(fd2[1]);
                if(!strcmp(request,"POST"))
                //如果是 POST 方法,那么通过写管道 fd1[1],将参数由标准输入传给 CGI 程序
                write(fd1[1],para,strlen(para));
                else 
                {
                    strcpy(envp[3],"QUERY_STRING=");
                    //如果是 GET 方法,那么通过设置环境变量传递参数
                    strcat(envp[3],para);
                }
                //等待子进程结束
                printf("$waiting for cgi's respond..........");
                waitpid(pid2,NULL,0);
                printf("child pid:%d finished!\n",pid2);
                //从管道 fd2[0]读取 cgi 程序的返回结果
                i=read(fd2[0],ret_buf,32768);
                if(i==0)
                {
                    printf(" read 0\n");
                    exit(1);
                }
                ret_buf[i]='\0' ;
                //将取得的返回结果发送给浏览器
                cbuf=ret_buf ;
                size=strlen(cbuf);
                totalSent=0 ;
                do 
                {
                    
                    bytesSent=send(acc_sock,cbuf+totalSent,i=strlen(cbuf+totalSent),0);
                    if(bytesSent==-1)break ;
                    totalSent+=bytesSent ;
                }
                while(totalSent<size);
                close(acc_sock);
            }
            else 
            {
                close(fd1[1]);
                close(fd2[0]);
                printf("$cgi handling.........\n");
                if(fd1[0]!=STDIN_FILENO)
                //重定向子进程的标准输入到 fd1[0]
                if(dup2(fd1[0],STDIN_FILENO)!=STDIN_FILENO)
                printf("dup2 error\n");
                if(fd2[1]!=STDOUT_FILENO)
                //重定向子进程的标准输出到 fd2[1]
                if(dup2(fd2[1],STDOUT_FILENO)!=STDOUT_FILENO)
                printf("dupe error\n");
                //装入 cgi 程序,传递环境变量,并运行
                execle(pathname,cginame,(char*)0,envp);
            }
            //end else
        }
        //end else
        close(acc_sock);
        exit(0);
    }
    close(acc_sock);
}
//end while
}
//end main

char*read_file(char*buf,int num_buf)
{
int i ;
char*cp,*cp2 ;
FILE*f ;
cp=buf+5 ;
cp2=strstr(cp," HTTP");
//使 cp2 指向路径名尾
if(cp2!=NULL)*cp2='\0' ;
//此时 cp 中存的即为浏览器所请求的文件路径名 
printf("$$$file: |%s|\n",cp);
f=fopen(cp,"r");
//只读方式打开文件
if(f==NULL)
{
    printf("Cannot open file %s !\n",cp);
    return error_return ;
}
i=fread(ret_buf,1,32768,f);
//读文件数据到 ret_buf
printf("%d bytes read from file %s\n",i,cp);
if(i==0)
{
    fclose(f);
    return error_return ;
}
ret_buf[i]='\0' ;
fclose(f);
return ret_buf ;
}
char*getcontentlen(char*buf)
{
char*cp1 ;
char*con_len ;
printf("into function getcontentlen \n");
cp1=strstr(buf,"Content-Length:");
cp1=cp1+16 ;
con_len=(char*)malloc(sizeof(char)*3);
//$$$$$$$$$$$$$$$$$$$$$$num control$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
strcpy(con_len,cp1);
*(con_len+2)='\0' ;
printf("con_len in function getcontent is :|%s|\n",con_len);
strcpy(envp[2],"CONTENT_LENGTH=");
strcat(envp[2],con_len);
printf("function getcontentlen finished\n");
// free(con_len);
return con_len ;
}

char*getcontenttype(char*buf)
{
char*cp1 ;
char*con_type ;
printf("into function getcontenttype \n");
cp1=strstr(buf,"Content-Type:");
cp1=cp1+14 ;
if((con_type=(char*)malloc(sizeof(char)*50))==NULL)
{
    printf("malloc error:getcontenttype\n");
    exit(0);
}
printf("malloc ok\n");
strcpy(con_type,cp1);
printf("copy ok\n");
*(con_type+33)='\0' ;
printf("con_type :|%s|\n",con_type);
strcpy(envp[0],"CONTENT_TYPE=");
strcat(envp[0],con_type);
printf("functoin getcontenttype finished\n");
return con_type ;
}
char*getpathname(char*buf,char*request)
{
char*cp1 ;
printf("into function getpathname\n");
// pathname=(char *)malloc(sizeof(char)*30);
if(!strncmp(request,"GET",3))
strcpy(pathname,buf+4);
else if(!strcmp(request,"POST"))
strcpy(pathname,buf+5);
else 
{
    printf("no GET nor POST\n");
    exit(0);
}
cp1=strstr(pathname," HTTP");
if(cp1==NULL)
{
    printf("the request grammer is false\n");
    //tell client
    exit(1);
}
*cp1='\0' ;
printf("pathname:|%s|\n",pathname);
//   return pathname;
return(char*)0 ;
}

char*getcginame(char*path)
{
char*cp1 ;
cp1=strstr(path,"cgi-bin");
cp1=cp1+8 ;
strcpy(cginame,cp1);
printf("cginame:|%s|\n",cginame);
return(char*)0 ;
}
char*getparaments(char*buf)
{
char*cp1 ;
cp1=strstr(buf,"name=");
strcpy(para,cp1);
printf("para:|%s|",para);
return(char*)0 ;
}

dome.c

/* CGI 程序 dome.c */
/* 这个 CGI 程序是用来处理 POST 请求的,它的功能是将用户所填写的信息提
取出来,并返回给用户 */
#include<stdio.h>
#include<stdlib.h>
#define MAX_ENTRIES 5 
typedef struct 
{
    char*name ;
    char*val ;
}
entry ;
//entry 中存储一对 name 和 value
char*fmakeword(FILE*f,char stop,int*len);
//从标准输入中读取字符直到下一个‘&’字符
char*makeword(char*line,char stop);
//从一个已经分离出‘name=value’对中分别提取出 name 和 value
void unescape_url(char*url);
//将 url 编码字符串中的十六进制编码转换成通常的字符
char x2c(char*what);
//把十六进制的字符串格式的数据转换成对应的字符串
void plustospace(char*str);
//将字符串中的‘+’转换成空格
main()
{
    entry entries[MAX_ENTRIES];
    /*HTML name/valur pairs */
    int x,cl,etnum ;
    printf("Content-type:text/html\n\n");
    //判断是否为 POST 请求
    if(strcmp(getenv("REQUEST_METHOD)"),"POST"))
    {
        printf("This script should be referenced with post METHODT. \n");
        exit(1);
    }
    //判断内容类型是否为 application/x-www-form-urlencoded
    if(strcmp(getenv("CONTENT_TYPE"),"application/x-www-form-urlencoded")
    )
    {
        printf("This script can only be used to decode form results.\n");
        exit(1);
    }
    cl=atoi(getenv("CONTENT_LENGTH"));
    //取出参数长度
    etnum=0 ;
    //一次读参数中的每个字符,分离出 name,value 及其他属性
    for(x=0;cl&&(!feof(stdin));x++)
    {
        entries[x].val=fmakeword(stdin,'&',&cl);
        plustospace(entries[x].val);
        unescape_url(entries[x].val);
        entries[x].name=makeword(entries[x].val,'=');
        etnum++;
    }
    
    //将返回结果有标准输出传给服务器的父进程
    printf("<HTML>\n");
    printf("<HEAD>\n");
    printf("<TITLE>HTML Form & CGI Script Demo Result</TITLE>\n");
    printf("</HEAD>\n");
    printf("<BODY>\n");
    printf("<H1>HTML Form & CGI Script Demo Result</H1>\n");
    printf("Here is a summary for your record of the information");
    printf("as we received it .\n");
    printf("<HR>\n");
    printf("<PRE>\n");
    printf("Name :                            %s\n",entries[0].val);
    printf("Address :                         %s\n",entries[1].val);
    printf("Zip :                             %s\n",entries[2].val);
    printf("Phone :                           %s\n",entries[3].val);
    printf("Email :                           %s\n",entries[4].val);
    // printf("\n");
    // printf("Computer Experience:%s\n",entries[5].val);
    printf("</PRE>\n");
    printf("</BODY>\n");
    printf("</HTML>\n");
}
char*fmakeword(FILE*f,char stop,int*cl)
{
    
    int wsize ;
    char*word ;
    int ll ;
    wsize=102400 ;
    ll=0 ;
    word=(char*)malloc(sizeof(char)*(wsize+1));
    while(1)
    {
        word[ll]=(char)fgetc(f);
        if(ll==wsize)
        {
            word[ll+1]='\0' ;
            wsize+=102400 ;
            word=(char*)realloc(word,sizeof(char)*(wsize+1));
        }
        --(*cl);
        if(word[ll]==stop||(feof(f))||(!(*cl)))
        {
            if(word[ll]!=stop)ll++;
            word[ll]='\0' ;
            return word ;
        }
        ++ll ;
    }
}

char*makeword(char*line,char stop)
{
    int x,y ;
    char*word=(char*)malloc(sizeof(char)*(strlen(line)+1));
    for(x=0;((line[x])&&(line[x]!=stop));x++)
    word[x]=line[x];
    word[x]='\0' ;
    if(line[x])
    ++x ;
    y=0 ;
    while(line[y++]=line[x++]);
    return word ;
}
void unescape_url(char*url)
{
    int x,y ;
    for(x=0,y=0;url[y];++x,++y)
    {
        if((url[x]=url[y])=='%')
        {
            url[x]=x2c(&url[y+1]);
            y+=2 ;
        }
    }
    url[x]='\0' ;
}
char x2c(char*what)
{
    char digit ;
    digit=(what[0]>='A'?((what[0]&0xdf)-'A')+10:(what[0]-'0'));
    digit+=16 ;
    digit+=(what[0]>'A'?((what[0]&0xdf)-'A')+10:(what[0]-'0'));
    return(digit);
}
void plustospace(char*str)
{
    int x ;
    for(x=0;str[x];x++)
    if(str[x]=='+')
    str[x]=' ' ;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值