Simple HTTP service on iPhone

Source: Internet
Author: User
Tags set socket htons

Original article:A simple, extensible HTTP server in cocoa

Address: http://cocoawithlove.com/2009/07/simple-extensible-http-server-in-cocoa.html

 

HTTP is a simple communication protocol between computers. On the iPhone, because there is no APIs for data synchronization and file sharing, the best way to achieve data transmission between iPhone applications and PCs is to embed an HTTP server in the program. In this post, I will demonstrate how to write a simple but scalable HTTP server. This server class can also be run on Mac.

Introduction

The example program runs as follows:

The program is simple: you can edit and save a text file (always saved in the same file ). When the program is still running, it will run an HTTP service on port 8080. If you request a "/" path, it will return the content of the text file. Other requests may cause a 501 error. To transfer text files from an iPhone program to a PC, simply enter the IP address of the iPhone in your browser and add the port number 8080.

 

Httpserver class and httpresponsehandler class

The HTTP server involves two types: Server (Listening for connection requests and reading data until the HTTP header ends), Response Processing (sending a response and reading data from the HTTP header ).

The server class and response class are designed to simplify the implementation of other response classes. You only need to implement these three methods:

  • Canhandlerequest: Method: URL: headerfields: Specifies whether the response will process a request.
  • Startresponse:Start to respond.
  • Load:-All subclasses should implement the + [nsobject load] method and register themselves with the base class.

This is the most basic HTTP server, but it allows you to quickly integrate HTTP Communication functions in your program.

 

Establish a socket listener

Most server communications, including HTTP, must begin with socket listening. Cocoa sockets can be fully implemented using bsdsockets code, but it is easier to use corefoundation cfsocket API. Unfortunately, although it has been simplified as much as possible-you still have to write a lot of modeled code to open a socket.

.

Httpserver start method:

Socket = cfsocketcreate (kcfallocatordefault, pf_inet, sock_stream,

Ipproto_tcp, 0, null, null );

If (! Socket)

{

[Self errorwithname: @ "unable to create socket."];

Return;

}

 

Int reuse = true;

Int filedescriptor = cfsocketgetnative (socket );

If (setsockopt (filedescriptor, sol_socket, so_reuseaddr,

(Void *) & reuse, sizeof (INT ))! = 0)

{

[Self errorwithname: @ "unable to set socket options."];

Return;

}

 

Struct sockaddr_in address;

Memset (& Address, 0, sizeof (Address ));

Address. sin_len = sizeof (Address );

Address. sin_family = af_inet;

Address. sin_addr.s_addr = htonl (inaddr_any );

Address. sin_port = htons (http_server_port );

Cfdataref addressdata =

Cfdatacreate (null, (const uint8 *) & Address, sizeof (Address ));

[(ID) addressdata autorelease];

 

If (cfsocketsetaddress (socket, addressdata )! = Kcfsocketsuccess)

{

[Self errorwithname: @ "unable to bind socket to address."];

Return;

}

So much code is just doing one thing: Open the socket and listen to the TCP connection from http_server_port (port 8080.

In addition, I used so_reuseaddr. This is to reuse opened ports (this is because, if we re-open the program immediately after the program crashes, the port is often occupied ).

 

Accept request

Once a socket is created, the process becomes simple. For each listener connection notification, we can construct an nsfilehandle from filedescriptor to accept the request.

Listeninghandle = [[nsfilehandle alloc]

Initwithfiledescriptor: filedescriptor

Closeondealloc: Yes];

 

[[Nsicationicationcenter defacenter center]

Addobserver: Self

Selector: @ selector (receiveincomingconnectionnotification :)

Name: nsfilehandleconnectionacceptednotification

Object: Nil];

[Listeninghandle acceptconnectioninbackgroundandnotify];

When the receiveincomingconnectionnotification: method is called, an nsfilehandle is created for each new request. You will find that:

  • For new connection requests listened by socket filedescriptor, a file handle (listeninghandle) is created from filedescriptor "manually ).
  • 1 file handle (listeninghandle) manually created from the socket filedesriptor to listen on the socket for new connections.
  • For each new connection received by listeninghandle, a file handle is automatically created. We constantly listen to these new handles (keys will be recorded in the inconmingrequests dictionary) and record the data of each connection.

Now we have received a new automatically created file handle. We have created an HTTP request message cfhttpmessageref (used to store request data ), and put these objects in the incomingrequests dictionary for the next access to cfhttpmessageref.

Cfhttpmessageref stores and parses the request data. , We can call the cfhttmessageheadercomplete () function for judgment until the HTTP header is complete and a response handler is generated.

In httpserverreceiveincomingdatanotification: The response handler is generated.

 

If (cfhttpmessageisheadercomplete (incomingrequest ))

{

Httpresponsehandler * Handler =

[Httpresponsehandler

Handlerforrequest: incomingrequest

Filehandle: incomingfilehandle

Server: Self];

[Responsehandlers addobject: Handler];

[Self stopreceivingforfilehandle: incomingfilehandle close: No];

 

[Handler startresponse];

Return;

}

The server stops listening to the connection and does not close it because file handle is passed to httpresponsehandler so that the HTTP response can be sent to the same file handle.

 

Elastic Response Processing

+ [Httpresponsehandlerhandlerforrequest: filehandle: SERVER:] Which subclass the method returns depends on the content of the response. It traverses the registered handlers array (sorted) and polls each handler to see which one is willing to process this request.

 

+ (Class) handlerclassforrequest :( cfhttpmessageref) arequest

Method :( nsstring *) requestmethod

URL :( nsurl *) requesturl

Headerfields :( nsdictionary *) requestheaderfields

{

For (class handlerclass in registeredhandlers)

{

If ([handlerclass canhandlerequest: arequest

Method: requestmethod

URL: requesturl

Headerfields: requestheaderfields])

{

Return handlerclass;

}

}

Return nil;

}

 

Therefore, all httpresponsehandlers must register with the base class. The simplest method is to register in the + nsobjectload method of each subclass.

 

+ (Void) Load

{

[Httpresponsehandler registerhandler: Self];

}

Here, the only response handler is apptextfileresponse. This class is responsible for processing requests whose requesturl is equal.

 

+ (Bool) canhandlerequest :( cfhttpmessageref) arequest

Method :( nsstring *) requestmethod

URL :( nsurl *) requesturl

Headerfields :( nsdictionary *) requestheaderfields

{

If ([requesturl. Path isw.tostring: @ "/"])

{

Return yes;

}

Return no;

}

 

Subsequently, apptextfileresponse performs a Synchronous Response in the startresponse method, and writes the text file content saved by the program to the response message.

 

-(Void) startresponse

{

Nsdata * filedata =

[Nsdata datawithcontentsoffile: [apptextfileresponse pathforfile];

 

Cfhttpmessageref response =

Cfhttpmessagecreateresponse (

Kcfallocatordefault, 200, null, kcfhttpversion1_1 );

Cfhttpmessagesetheaderfieldvalue (

Response, (cfstringref) @ "Content-Type", (cfstringref) @ "text/plain ");

Cfhttpmessagesetheaderfieldvalue (

Response, (cfstringref) @ "connection", (cfstringref) @ "close ");

Cfhttpmessagesetheaderfieldvalue (

Response,

(Cfstringref) @ "Content-Length ",

(Cfstringref) [nsstring stringwithformat: @ "% lD", [filedata length]);

Cfdataref headerdata = cfhttpmessagecopyserializedmessage (response );

 

@ Try

{

[Filehandle writedata :( nsdata *) headerdata];

[Filehandle writedata: filedata];

}

@ Catch (nsexception * exception)

{

// Ignore the exception, it normally just means the client

// Closed the connection from the other end.

}

@ Finally

{

Cfrelease (headerdata );

[Server closehandler: Self];

}

}

 

[Serverclosehandler: Self]; tells the server to remove httpresponsehandler from the current handlers.

The server will call endresponse to remove handler (when the connection is closed -- Because handler does not support keep-alive, that is, regular connection ).

 

To be improved

The largest task parsing HTTP Request body is not implemented. The normal HTTP body parsing process is very complicated. The length of the HTTP body is specified in the Content-Length header, but may not be specified-so it cannot end until the HTTP body ends. The HTTP body may also be encoded. There are various encoding methods: Chunk, quoted-printable, base64, and gzip. Each method is completely different.

I never thought about implementing a universal solution. Generally, it depends on your actual needs. You can process the Request body in the receiveincomingdatanotification: Method of httprequesthandler. By default, all data after the HTTP request header is ignored.

Tip:

Httprequesthandlerreceiveincomingdatanotification: when the method is called for the first time, the start byte of the HTTP body has been obtained from filehandle and added to the request instance variable. If you want to get the HTTP body, either read it into the request object or add the starting byte.

Another thing that is not processed is the frequent connection to keep-alive. This is also handled in the-[httprequesthandler receiveincomingdatanotification:] method, where I have made comments. In fact, the simple method is to set the connection HTTP header for each response and tell the client not to process keep-alive. Httpreseponsehandler does not use the Content-Type header in the request. If you want to handle this, you should process it in the + [httpresponsehandler handlerclassforrequest: Method: URL: headerfields:] method.

Finally, the server does not process SSL/TLS. Because in the local network, data transmission is relatively secure. If you want to provide a secure link in the open Internet, you need to make a lot of changes at the socket layer. If security is very important to you, you may not implement the server by yourself-if possible, use a mature tlshttp server and only process it on the client. It is easy to use cocoa to process client security-it is completely automatic and transparent through cfreadstream and nsurlconnection.

 

Conclusion

Download: The sample app texttransfer.zip (45kb), including the httpserver and httpresponsehandler classes. Although the mainstream HTTP server is a huge and complex software, it does not mean that it must be huge and complex-The implementation in this article only has two classes, but it can also be configured and expanded.

Of course, our goal is not to use it as a complex web server. It applies to your iPhone or Mac program as a lightweight data sharing portal.

From: http://blog.csdn.net/smallsky_keke/article/details/7311024

Original article:A simple, extensible HTTP server in cocoa

Address: http://cocoawithlove.com/2009/07/simple-extensible-http-server-in-cocoa.html

 

HTTP is a simple communication protocol between computers. On the iPhone, because there is no APIs for data synchronization and file sharing, the best way to achieve data transmission between iPhone applications and PCs is to embed an HTTP server in the program. In this post, I will demonstrate how to write a simple but scalable HTTP server. This server class can also be run on Mac.

Introduction

The example program runs as follows:

The program is simple: you can edit and save a text file (always saved in the same file ). When the program is still running, it will run an HTTP service on port 8080. If you request a "/" path, it will return the content of the text file. Other requests may cause a 501 error. To transfer text files from an iPhone program to a PC, simply enter the IP address of the iPhone in your browser and add the port number 8080.

 

Httpserver class and httpresponsehandler class

The HTTP server involves two types: Server (Listening for connection requests and reading data until the HTTP header ends), Response Processing (sending a response and reading data from the HTTP header ).

The server class and response class are designed to simplify the implementation of other response classes. You only need to implement these three methods:

  • Canhandlerequest: Method: URL: headerfields: Specifies whether the response will process a request.
  • Startresponse:Start to respond.
  • Load:-All subclasses should implement the + [nsobject load] method and register themselves with the base class.

This is the most basic HTTP server, but it allows you to quickly integrate HTTP Communication functions in your program.

 

Establish a socket listener

Most server communications, including HTTP, must begin with socket listening. Cocoa sockets can be fully implemented using bsdsockets code, but it is easier to use corefoundation cfsocket API. Unfortunately, although it has been simplified as much as possible-you still have to write a lot of modeled code to open a socket.

.

Httpserver start method:

Socket = cfsocketcreate (kcfallocatordefault, pf_inet, sock_stream,

Ipproto_tcp, 0, null, null );

If (! Socket)

{

[Self errorwithname: @ "unable to create socket."];

Return;

}

 

Int reuse = true;

Int filedescriptor = cfsocketgetnative (socket );

If (setsockopt (filedescriptor, sol_socket, so_reuseaddr,

(Void *) & reuse, sizeof (INT ))! = 0)

{

[Self errorwithname: @ "unable to set socket options."];

Return;

}

 

Struct sockaddr_in address;

Memset (& Address, 0, sizeof (Address ));

Address. sin_len = sizeof (Address );

Address. sin_family = af_inet;

Address. sin_addr.s_addr = htonl (inaddr_any );

Address. sin_port = htons (http_server_port );

Cfdataref addressdata =

Cfdatacreate (null, (const uint8 *) & Address, sizeof (Address ));

[(ID) addressdata autorelease];

 

If (cfsocketsetaddress (socket, addressdata )! = Kcfsocketsuccess)

{

[Self errorwithname: @ "unable to bind socket to address."];

Return;

}

So much code is just doing one thing: Open the socket and listen to the TCP connection from http_server_port (port 8080.

In addition, I used so_reuseaddr. This is to reuse opened ports (this is because, if we re-open the program immediately after the program crashes, the port is often occupied ).

 

Accept request

Once a socket is created, the process becomes simple. For each listener connection notification, we can construct an nsfilehandle from filedescriptor to accept the request.

Listeninghandle = [[nsfilehandle alloc]

Initwithfiledescriptor: filedescriptor

Closeondealloc: Yes];

 

[[Nsicationicationcenter defacenter center]

Addobserver: Self

Selector: @ selector (receiveincomingconnectionnotification :)

Name: nsfilehandleconnectionacceptednotification

Object: Nil];

[Listeninghandle acceptconnectioninbackgroundandnotify];

When the receiveincomingconnectionnotification: method is called, an nsfilehandle is created for each new request. You will find that:

  • For new connection requests listened by socket filedescriptor, a file handle (listeninghandle) is created from filedescriptor "manually ).
  • 1 file handle (listeninghandle) manually created from the socket filedesriptor to listen on the socket for new connections.
  • For each new connection received by listeninghandle, a file handle is automatically created. We constantly listen to these new handles (keys will be recorded in the inconmingrequests dictionary) and record the data of each connection.

Now we have received a new automatically created file handle. We have created an HTTP request message cfhttpmessageref (used to store request data ), and put these objects in the incomingrequests dictionary for the next access to cfhttpmessageref.

Cfhttpmessageref stores and parses the request data. , We can call the cfhttmessageheadercomplete () function for judgment until the HTTP header is complete and a response handler is generated.

In httpserverreceiveincomingdatanotification: The response handler is generated.

 

If (cfhttpmessageisheadercomplete (incomingrequest ))

{

Httpresponsehandler * Handler =

[Httpresponsehandler

Handlerforrequest: incomingrequest

Filehandle: incomingfilehandle

Server: Self];

[Responsehandlers addobject: Handler];

[Self stopreceivingforfilehandle: incomingfilehandle close: No];

 

[Handler startresponse];

Return;

}

The server stops listening to the connection and does not close it because file handle is passed to httpresponsehandler so that the HTTP response can be sent to the same file handle.

 

Elastic Response Processing

+ [Httpresponsehandlerhandlerforrequest: filehandle: SERVER:] Which subclass the method returns depends on the content of the response. It traverses the registered handlers array (sorted) and polls each handler to see which one is willing to process this request.

 

+ (Class) handlerclassforrequest :( cfhttpmessageref) arequest

Method :( nsstring *) requestmethod

URL :( nsurl *) requesturl

Headerfields :( nsdictionary *) requestheaderfields

{

For (class handlerclass in registeredhandlers)

{

If ([handlerclass canhandlerequest: arequest

Method: requestmethod

URL: requesturl

Headerfields: requestheaderfields])

{

Return handlerclass;

}

}

Return nil;

}

 

Therefore, all httpresponsehandlers must register with the base class. The simplest method is to register in the + nsobjectload method of each subclass.

 

+ (Void) Load

{

[Httpresponsehandler registerhandler: Self];

}

Here, the only response handler is apptextfileresponse. This class is responsible for processing requests whose requesturl is equal.

 

+ (Bool) canhandlerequest :( cfhttpmessageref) arequest

Method :( nsstring *) requestmethod

URL :( nsurl *) requesturl

Headerfields :( nsdictionary *) requestheaderfields

{

If ([requesturl. Path isw.tostring: @ "/"])

{

Return yes;

}

Return no;

}

 

Subsequently, apptextfileresponse performs a Synchronous Response in the startresponse method, and writes the text file content saved by the program to the response message.

 

-(Void) startresponse

{

Nsdata * filedata =

[Nsdata datawithcontentsoffile: [apptextfileresponse pathforfile];

 

Cfhttpmessageref response =

Cfhttpmessagecreateresponse (

Kcfallocatordefault, 200, null, kcfhttpversion1_1 );

Cfhttpmessagesetheaderfieldvalue (

Response, (cfstringref) @ "Content-Type", (cfstringref) @ "text/plain ");

Cfhttpmessagesetheaderfieldvalue (

Response, (cfstringref) @ "connection", (cfstringref) @ "close ");

Cfhttpmessagesetheaderfieldvalue (

Response,

(Cfstringref) @ "Content-Length ",

(Cfstringref) [nsstring stringwithformat: @ "% lD", [filedata length]);

Cfdataref headerdata = cfhttpmessagecopyserializedmessage (response );

 

@ Try

{

[Filehandle writedata :( nsdata *) headerdata];

[Filehandle writedata: filedata];

}

@ Catch (nsexception * exception)

{

// Ignore the exception, it normally just means the client

// Closed the connection from the other end.

}

@ Finally

{

Cfrelease (headerdata );

[Server closehandler: Self];

}

}

 

[Serverclosehandler: Self]; tells the server to remove httpresponsehandler from the current handlers.

The server will call endresponse to remove handler (when the connection is closed -- Because handler does not support keep-alive, that is, regular connection ).

 

To be improved

The largest task parsing HTTP Request body is not implemented. The normal HTTP body parsing process is very complicated. The length of the HTTP body is specified in the Content-Length header, but may not be specified-so it cannot end until the HTTP body ends. The HTTP body may also be encoded. There are various encoding methods: Chunk, quoted-printable, base64, and gzip. Each method is completely different.

I never thought about implementing a universal solution. Generally, it depends on your actual needs. You can process the Request body in the receiveincomingdatanotification: Method of httprequesthandler. By default, all data after the HTTP request header is ignored.

Tip:

Httprequesthandlerreceiveincomingdatanotification: when the method is called for the first time, the start byte of the HTTP body has been obtained from filehandle and added to the request instance variable. If you want to get the HTTP body, either read it into the request object or add the starting byte.

Another thing that is not processed is the frequent connection to keep-alive. This is also handled in the-[httprequesthandler receiveincomingdatanotification:] method, where I have made comments. In fact, the simple method is to set the connection HTTP header for each response and tell the client not to process keep-alive. Httpreseponsehandler does not use the Content-Type header in the request. If you want to handle this, you should process it in the + [httpresponsehandler handlerclassforrequest: Method: URL: headerfields:] method.

Finally, the server does not process SSL/TLS. Because in the local network, data transmission is relatively secure. If you want to provide a secure link in the open Internet, you need to make a lot of changes at the socket layer. If security is very important to you, you may not implement the server by yourself-if possible, use a mature tlshttp server and only process it on the client. It is easy to use cocoa to process client security-it is completely automatic and transparent through cfreadstream and nsurlconnection.

 

Conclusion

Download: The sample app texttransfer.zip (45kb), including the httpserver and httpresponsehandler classes. Although the mainstream HTTP server is a huge and complex software, it does not mean that it must be huge and complex-The implementation in this article only has two classes, but it can also be configured and expanded.

Of course, our goal is not to use it as a complex web server. It applies to your iPhone or Mac program as a lightweight data sharing portal.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.