前言
超级详细的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-lineheaders ( 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]=' ' ;
}