Linux下以C构建WEB服务并响应XHR(XMLHttpRequest)请求
网页要与硬件打交道,通常的方式是使用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的形式监听服务端口。
/* 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);
}
}
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; }4.响应文件请求
注意:最关键的就是构建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);//发送响应数据 }5.分析XHR的FormData
(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; }2)分析FormData的具体数据
注:这里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.csdn.net/detail/xxdddail/6889831
转载请注明出处http://blog.csdn.net/xxdddail/article/details/18841325
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。