Linux下以C构建WEB服务并响应XHR(XMLHttpRequest)请求

网页要与硬件打交道,通常的方式是使用CGI,那除了CGI之外,还有没有其他的方式呢?我们知道XHR是可以在不提交表单的情况下,实现与WEB服务端的交互的,那么服务端除了CGI来作出响应外,还有没有其他的方法呢?

答案是有的,我们先来看效果图。

1.XHR的POST请求效果图

Linux下以C构建WEB服务并响应XHR(XMLHttpRequest)请求

Linux下以C构建WEB服务并响应XHR(XMLHttpRequest)请求

2.XHR的GET请求效果图

Linux下以C构建WEB服务并响应XHR(XMLHttpRequest)请求

Linux下以C构建WEB服务并响应XHR(XMLHttpRequest)请求

因为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;
}


3.依据URL的文件请求读取文件数据

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(&current);
  timeinfo = (struct tm *)localtime(&current);
  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



Linux下以C构建WEB服务并响应XHR(XMLHttpRequest)请求

上一篇:79. 视图里的计算属性——从ASP、JSP、JSF到XPages的“钻石”


下一篇:POJ 3694 Network