网页要与硬件打交道,通常的方式是使用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" οnclick="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