网页要与硬件打交道,通常的方式是使用CGI,那除了CGI之外,还有没有其他的方式呢?我们知道XHR是可以在不提交表单的情况下,实现与WEB服务端的交互的,那么服务端除了CGI来作出响应外,还有没有其他的方法呢?
答案是有的,我们先来看效果图。
1.XHR的POST请求效果图
2.XHR的GET请求效果图
因为WEB的交互在本质上就是HTTP请求,既然是HTTP请求,那么我们只要以HTTP的形式作出回应,那不就可以了吗?
再思考一个问题,XHR请求有两种方式,一种是GET,一种是POST。这和表单的提交方式是相似的。如果有注意观察,就会发现,提交表单时采用GET请求时,表单数据会跟在URL后面,以问号作为开始,并以KEY-VALUE对的形式以&符号分隔多个KEY-VALUE对。而采用POST方法时,则不是这样的。
那POST的数据又是如何提交出去的?服务端收到的数据又会是什么 样的呢?为此,我以C构建了一个简单的WEB服务来响应XHR的POST请求。下面是实现的步骤。
1.在服务端以SOCKET的形式监听服务端口。
- <span style="font-size:10px;">/* server.c */
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #define MAXLINE 1024
- #define FILE_NAME_LEN_MAX 256
- #define DEFEULT_SERVER_PORT 8000
- #define RESPONSE_HEADER_LENGTH_MAX 1024
- #define BOUNDARY_LEN_MAX 256
- #define KEY_LEN_MAX 256
- #define VALUE_LEN_MAX 1024
- #define BACK_LEN_MAX 10240
- #define FORM_DATA_LEN_MAX 10240
- struct FormData
- {
- char Key[KEY_LEN_MAX];
- char Value[VALUE_LEN_MAX];
- struct FormData *Next;
- };
- char * fetchMethod(const char * buf);
- int hasFormDataInUrl(const char * buf,int bufLen);
- char * fetchFileName(const char * buf,int bufLen);
- char * readFileBytes(const char * fileName);
- char * fetchBoundary(const char * buf,int bufLen);
- void handleFormData(int connfd,const char * buf,const char * boundary,const int isFormDataInUrl/*0:not.1:is*/);
- struct FormData * fetchFormData(const char * buf,const char *boundary);
- struct FormData * fetchFormDataInUrl(const char * buf);
- void responseFormData(int connfd,struct FormData * head,const char *method);
- void response(int connfd,char *responseContent,int responseContentLength,char *responseCode,char *contentType);
- int main(int argc, char *argv[])
- {
- struct sockaddr_in servaddr, cliaddr;
- socklen_t cliaddr_len;
- int listenfd, connfd;
- char buf[MAXLINE];
- char *data;
- char *responseContent;
- char str[INET_ADDRSTRLEN];
- char *method;
- char *fileName;
- char *boundary;
- int i,n;
- int port= DEFEULT_SERVER_PORT;
- if(argc>1)
- {
- port=atoi(argv[1]);//Input port
- }
- if(port<=0||port>0xFFFF)
- {
- printf("Port(%d) is out of range(1-%d)\n",port,0xFFFF);
- return;
- }
- listenfd = socket(AF_INET, SOCK_STREAM, 0);
- bzero(&servaddr, sizeof(servaddr));
- servaddr.sin_family = AF_INET;
- servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
- servaddr.sin_port = htons(port);
- bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
- listen(listenfd, 20);
- printf("Listen %d\nAccepting connections ...\n",port);
- while (1)
- {
- cliaddr_len = sizeof(cliaddr);
- connfd = accept(listenfd,
- (struct sockaddr *)&cliaddr, &cliaddr_len);
- n = read(connfd, buf, MAXLINE);
- printf("---------------------\n");
- printf("received from %s at PORT %d\n",
- inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
- ntohs(cliaddr.sin_port));
- printf("read:%d\n%s\n",n,buf);
- method=fetchMethod(buf);
- printf("method:%s\n",method);
- if(strcmp(method,"GET")&&strcmp(method,"POST"))
- {
- response(connfd,"Only support GET/POST",21,"200 OK","text/html");
- close(connfd);
- continue;
- }
- if(hasFormDataInUrl(buf,n))//Check from data in url
- {
- handleFormData(connfd,buf,boundary,1);//Directly response if has form data in url.(GET Method)
- close(connfd);
- continue;
- }
- fileName=fetchFileName(buf,n);
- printf("Request file name:%s\n",fileName);
- responseContent=readFileBytes(fileName);
- if(responseContent)//Response if has content.
- {
- //printf("response content:%s\n",responseContent);
- response(connfd,responseContent,strlen(responseContent),"200 OK", "text/html");
- close(connfd);
- continue;
- }
- boundary=fetchBoundary(buf,n);//If no content,web may sumbit form data.Fetch boundary firstly if has form data.
- if((!boundary)||(strlen(boundary)<=0))//No content and no boundary,file is not exist.
- {
- printf("Request file not exist!\n");
- response(connfd,"404 Not Found",13,"200 OK","text/html");
- close(connfd);
- continue;
- }
- handleFormData(connfd,buf,boundary,0);//POST method.Form data is between boundaries.
- close(connfd);
- }
- }</span>
2.分析请求的方法是否为GET或POST
- char * fetchMethod(const char * buf)
- {
- int i;
- char *method;
- if(!buf)
- {
- return NULL;
- }
- method=(char *)malloc(5);
- memset(method,'\0',5);
- for(i=0;i<5;i++)
- {
- if(buf[i]=='/'||buf[i]==0x20/*space*/)
- {
- break;
- }
- method[i]=buf[i];
- }
- return method;
- }
- char * fetchFileName(const char * buf,int bufLen)
- {
- char *fileName;
- int i=0, j=-1;
- if(!buf)
- {
- return NULL;
- }
- if(bufLen<=0)
- {
- return NULL;
- }
- fileName=(char *)malloc(FILE_NAME_LEN_MAX*sizeof(char));
- memset(fileName,'\0',FILE_NAME_LEN_MAX);
- //printf("\n---------------\n");
- for(i=0;i<bufLen;i++)
- {
- //printf("%c",buf[i]);
- if(buf[i]=='/')
- {
- j=0;
- continue;
- }
- if(j<0)
- {
- continue;
- }
- if(buf[i]==0x20)
- {
- break;
- }
- fileName[j]=buf[i];
- j++;
- }
- //printf("\n---------------\n");
- return fileName;
- }
- char * readFileBytes(const char * fileName)
- {
- int contentLen=0;
- char *content;
- if(!fileName)
- {
- return NULL;
- }
- FILE *f=fopen(fileName,"r");
- if(!f)
- {
- return NULL;
- }
- fseek(f, 0,SEEK_END);
- contentLen=ftell(f);
- content=(char *)malloc(contentLen*sizeof(char));
- memset(content,'\0',contentLen);
- fseek(f,0,SEEK_SET);
- fread(content,1,contentLen,f);
- fclose(f);
- return content;
- }
注意:最关键的就是构建HTTP的头
- void response(int connfd,char *responseContent,int responseContentLength,char *responseCode,char *contentType)
- {
- char responseHeader [RESPONSE_HEADER_LENGTH_MAX];
- int headerLen=0;
- int offset=0;
- memset(responseHeader,'\0',RESPONSE_HEADER_LENGTH_MAX);
- //HTTP头构建开始
- //HTTP
- strcpy(responseHeader+offset,"HTTP/1.1 ");
- offset+=strlen("HTTP/1.1 ");
- strcpy(responseHeader+offset,responseCode);
- offset+=strlen(responseCode);
- strcpy(responseHeader+offset,"\r\n");
- offset+=strlen("\r\n");
- //Server
- strcpy(responseHeader+offset, "Server: My Web Server\r\n");
- offset+=strlen("Server: My Web Server\r\n");
- //Content length
- strcpy(responseHeader+offset,"Content-Length: ");
- offset+=strlen("Content-Length: ");
- printf("content length=%d\n",responseContentLength);
- //strcpy(responseHeader+offset,(const char*)&responseContentLength);
- sprintf(responseHeader+offset,"%d",responseContentLength);
- offset+=sizeof(int);
- strcpy(responseHeader+offset,"\r\n");
- offset+=strlen("\r\n");
- //Connection
- strcpy(responseHeader+offset,"Connection: Keep-Alive\r\n");
- offset+=strlen("Connection: Keep-Alive\r\n");
- //Content type
- strcpy(responseHeader+offset,"Content-Type: ");
- offset+=strlen("Content-Type: ");
- strcpy(responseHeader+offset,contentType);
- offset+=strlen(contentType);
- strcpy(responseHeader+offset,"\r\n\r\n");
- offset+=strlen("\r\n\r\n");
- headerLen=offset;
- //HTTP头构建结束
- //printf("Response Header:%s\n",responseHeader);
- write(connfd,responseHeader,headerLen);//发送HTTP头
- write(connfd,responseContent,responseContentLength);//发送响应数据
- }
(1)XHR的POST请求:POST请求的数据是以FormData的形式发送的,在服务端会收到相应的数据,下面是具体的分析代码。
1)分析FormData的分界线(boundary)
- char * fetchBoundary(const char * buf,int bufLen)
- {
- char *boundaryBegin;
- char *boundaryTemp;
- char *boundary;
- const char boundaryKey[]="boundary=";
- int i;
- if(!buf)
- {
- return NULL;
- }
- if(!strstr(buf,"multipart/form-data"))
- {
- return NULL;
- }
- boundaryBegin=strstr(buf,boundaryKey);
- if(!boundaryBegin)
- {
- return NULL;
- }
- i=0;
- //printf("###########################\n");
- boundaryTemp=(char *)malloc(BOUNDARY_LEN_MAX);
- memset(boundaryTemp,'\0',BOUNDARY_LEN_MAX);
- boundaryBegin+=strlen(boundaryKey);//move pointer.
- while(1)
- {
- boundaryTemp[i]=boundaryBegin[i];
- if(boundaryBegin[i]==0x0A)
- {
- break;
- }
- i++;
- }
- boundary=(char *)malloc(i);
- strcpy(boundary,boundaryTemp);
- //printf("boundary:%s\n",boundary);
- //printf("###########################\n");
- return boundary;
- }
注:这里FormData的数据是以链表的形式储存的,FormData的结构见前面的代码。
- struct FormData * fetchFormData(const char * buf,const char *boundary)
- {
- char * begin;
- char * end;
- char * formData;
- char line[VALUE_LEN_MAX];
- char * bufTemp;
- char * temp;
- char * fromData;
- char split[]={0x0D,0x0A};
- char keyFlag[]="Content-Disposition: form-data; name=";
- int i,j,n,boundaryLen,bufLen;
- struct FormData *head,*current,*next;
- if(!buf)
- {
- return;
- }
- if(!boundary)
- {
- return;
- }
- printf("****************Form Data**************************\n");
- boundaryLen=strlen(boundary);
- begin=(char *)malloc(boundaryLen+2+1);//2 is prefix "--"
- memset(begin,'-',boundaryLen+2);//begin boundary prefix "--"
- memcpy(begin+2,boundary,boundaryLen);
- end=(char *)malloc(boundaryLen+4+1);//4 is prefix "--" and suffix "--"
- memset(end,'-',boundaryLen+4);
- memcpy(end+2,boundary,boundaryLen);
- bufLen=strlen(buf);
- bufTemp=(char *)malloc(bufLen*sizeof(char));
- memset(bufTemp,0x0,bufLen);
- memcpy(bufTemp,buf,bufLen);
- formData=strstr(bufTemp,begin);
- i=0;
- n=strlen(formData);
- memset(line,0,VALUE_LEN_MAX);
- head=(struct FormData *)malloc(sizeof(struct FormData));
- head->Next=NULL;
- current=head;
- next=NULL;
- for(i=0,j=0;i<n;i++)
- {
- if(formData[i]!=0x0A&&formData[i]!=0x0D)//Not new line.
- {
- line[j++]=formData[i];
- continue;
- }
- j=0;
- if(strlen(line)<=0)
- {
- memset(line,0,VALUE_LEN_MAX);
- continue;
- }
- if(strstr(line,end))
- {
- break;
- }
- //printf("line:%s\n",line);
- if(*line==*begin)
- {
- next=(struct FormData*)malloc(sizeof(struct FormData));
- next->Next=NULL;
- current->Next=next;
- memset(line,0,VALUE_LEN_MAX);
- continue;
- }
- temp=strstr(line,keyFlag);
- if(temp)
- {
- strncpy(current->Key,temp+strlen(keyFlag)+1,strlen(line)-strlen(keyFlag)-2);//kick out quote of key.
- }
- else
- {
- strcpy(current->Value,line);
- current=next;
- }
- memset(line,0,VALUE_LEN_MAX);
- }
- current=head;
- while(current!=NULL)
- {
- if(strlen(current->Key)>0)
- {
- printf("Name:%s Data:%s\n",current->Key,current->Value);
- }
- current=current->Next;
- }
- printf("*********************************************\n");
- return head;
- }
2.XHR的GET请求:GET的请求的数据是含在URL中的,所以FormData需要从URL中分析得到。
注:对于XHR的GET请求,如果也以FormData的形式发送(即:xhr.open("GET",url,true);xhr.send(formData);),那么在服务端没有办法直接在服务端获取到数据。原因可能是GET请求的数据是以URL的形式来发出的,但在服务端却没法收到这一数据。不过,还是有一个变通的方法。既然GET请求是以URL形式发出请求的,那我何不直接构建成像GET请求形式的URL,再以XHR的形式发送呢?结果证明是可行的,效果图见前面。
- int hasFormDataInUrl(const char * buf,int bufLen)
- {
- int i;
- if(!buf)
- {
- return 0;
- }
- printf("===========Check form data in url===============\n");
- for(i=0;i<bufLen;i++)
- {
- if(buf[i]==0x0D||buf[i]==0x0A)//Only check first line.
- {
- break;
- }
- if(buf[i]=='?')
- {
- return 1;
- }
- }
- printf("=================================================\n");
- return 0;
- }
- struct FormData * fetchFormDataInUrl(const char * buf)
- {
- struct FormData *head,*current,*next;
- int i,keyIndex,valueIndex;
- if(!buf)
- {
- return NULL;
- }
- head=(struct FormData *)malloc(sizeof(struct FormData));
- head->Next=NULL;
- current=head;
- next=NULL;
- printf("****************Form Data**************************\n");
- for(i=0,keyIndex=-1,valueIndex=-1;;i++)
- {
- if(buf[i]==0x0D||buf[i]==0x0A)//Data is in first line.
- {
- break;
- }
- if(buf[i]=='?'||buf[i]=='&')
- {
- keyIndex=0;
- valueIndex=-1;
- next=(struct FormData*)malloc(sizeof(struct FormData));
- next->Next=NULL;
- current->Next=next;
- if(buf[i]=='&')
- {
- current=next;
- }
- continue;
- }
- if(buf[i]=='=')
- {
- keyIndex=-1;
- valueIndex=0;
- continue;
- }
- if(keyIndex>=0)
- {
- current->Key[keyIndex++]=buf[i];
- }
- if(valueIndex>=0)
- {
- current->Value[valueIndex++]=buf[i];
- }
- }
- current=head;
- while(current!=NULL)
- {
- if(strlen(current->Key)>0)
- {
- printf("Name:%s Data:%s\n",current->Key,current->Value);
- }
- current=current->Next;
- }
- printf("*********************************************\n");
- return head;
- }
6.响应XHR的请求
注:在响应请求时,除了将收到的数据回馈回去之外,还回馈了服务端的时间。
- void responseFormData(int connfd,struct FormData * head,const char *method)
- {
- time_t current;
- struct tm *timeinfo;
- char backData[BACK_LEN_MAX];
- char sectionFlag[]="\r\n";
- int n;
- if(!head)
- {
- return;
- };
- memset(backData,0, BACK_LEN_MAX);
- sprintf(backData,"Method:%s%s",method,sectionFlag);
- n=strlen("Method")+strlen(method)+strlen(sectionFlag);
- while(head!=NULL)
- {
- if(strlen(head->Key)>0)
- {
- sprintf(backData+n,"%s:%s%s",head->Key,head->Value,sectionFlag);
- n+=strlen(head->Key)+strlen(head->Value)+strlen(sectionFlag);
- }
- head=head->Next;
- }
- time(¤t);
- timeinfo = (struct tm *)localtime(¤t);
- sprintf(backData+n,"Server time:%s%s",asctime(timeinfo),sectionFlag);
- response(connfd,backData,strlen(backData),"200 OK","text/html");
- }
- void handleFormData(int connfd,const char * buf,const char * boundary,const int isFormDataInUrl/*0:not.1:is*/)
- {
- struct FormData * head;
- char *method;
- if(isFormDataInUrl)
- {
- head=fetchFormDataInUrl(buf);
- }
- else
- {
- head=fetchFormData(buf,boundary);
- }
- method=fetchMethod(buf);
- responseFormData(connfd,head,method);
- }
附上XHR的WEB实现
xhr.html
- <html>
- <head>
- <title>Async request test</title>
- <script type="text/javascript" src="xhr.js"></script>
- <script type="text/javascript">
- function Request() {
- var url = document.getElementById("url").value;
- var data1 = document.getElementById("data1").value;
- var data2 = document.getElementById("data2").value;
- var method=document.getElementById("method").value;
- var formData =null;
- if(method==="POST")
- {
- formData=new FormData();
- formData.append("Data1", data1);
- formData.append("Data2", data2);
- }
- else if(method==="GET")
- {
- url+="/?Data1="+data1+"&Data2="+data2;
- }
- else
- {
- alert(method+" not support");
- return;
- }
- Send(url, function (e) {
- alert(e);
- },method,formData);
- }
- </script>
- </head>
- <body>
- <div>
- <a> URL:</a><input id="url" type="text" value="http://192.168.80.131:16800" style="width:200px;"/>
- </div>
- <div>
- <a>Data1:</a><input id="data1" type="text" value="ABCDEFG" style="width:200px;"/>
- </div>
- <div>
- <a>Data2:</a><input id="data2" type="text" value="1234567" style="width:200px;"/>
- </div>
- <div>
- <a>Method:</a><input id="method" type="text" value="GET" style="width:200px;"/>
- </div>
- <div>
- <input type="button" value="XHR" onclick="Request()" />
- </div>
- </body>
- </html>
xhr.js
- function Send(url, callback,method,formData) {
- var xhr = new XMLHttpRequest();
- xhr.onreadystatechange = function () {
- if (xhr.readyState == 4) {
- if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) //200:Success.304:Tell browser to read cache.
- {
- if (callback === undefined || callback === null) {
- return;
- }
- callback(xhr.responseText);
- }
- else {
- alert(xhr.responseText);
- }
- }
- }
- xhr.open(method, url, true);
- if (formData===undefined) {
- formData = null;
- }
- if(method==="GET")
- {
- xhr.send(null);
- }
- else
- {
- xhr.send(formData);
- }
- }
以上是WEB服务及响应XHR请求的简单实现,实现过程中的代码有很多需要改善的地方,请各路大牛多多指点。
源码可以此处下载http://download.youkuaiyun.com/detail/xxdddail/6889831
转载请注明出处http://blog.youkuaiyun.com/xxdddail/article/details/18841325