NanoHTTPD是一轻型服务器,可以再Android手机中构建一web服务器。NanoHTTPD是一个开源的项目,下面我们对其流程进行分析:
public NanoHTTPD( int port, File wwwroot ) throws IOException
{
myTcpPort = port;
this.myRootDir = wwwroot;
myServerSocket = new ServerSocket( myTcpPort );
myThread = new Thread( new Runnable()
{
public void run()
{
try
{
while( true )
new HTTPSession( myServerSocket.accept());
}
catch ( IOException ioe )
{}
}
});
myThread.setDaemon( true );
myThread.start();
}
public HTTPSession( Socket s )
{
mySocket = s;
Thread t = new Thread( this );
t.setDaemon( true );
t.start();
}
public void run()
{
try
{
InputStream is = mySocket.getInputStream();
if ( is == null) return;
// Read the first 8192 bytes.
// The full header should fit in here.
// Apache's default header limit is 8KB.
int bufsize = 8192;
byte[] buf = new byte[bufsize];
int rlen = is.read(buf, 0, bufsize);
if (rlen <= 0) return;
// Create a BufferedReader for parsing the header.
ByteArrayInputStream hbis = new ByteArrayInputStream(buf, 0, rlen);
BufferedReader hin = new BufferedReader( new InputStreamReader( hbis ));
Properties pre = new Properties();
Properties parms = new Properties();
Properties header = new Properties();
Properties files = new Properties();
// Decode the header into parms and header java properties
decodeHeader(hin, pre, parms, header);
String method = pre.getProperty("method");
String uri = pre.getProperty("uri");
long size = 0x7FFFFFFFFFFFFFFFl;
String contentLength = header.getProperty("content-length");
if (contentLength != null)
{
try { size = Integer.parseInt(contentLength); }
catch (NumberFormatException ex) {}
}
// We are looking for the byte separating header from body.
// It must be the last byte of the first two sequential new lines.
int splitbyte = 0;
boolean sbfound = false;
while (splitbyte < rlen)
{
if (buf[splitbyte] == '\r' && buf[++splitbyte] == '\n' && buf[++splitbyte] == '\r' && buf[++splitbyte] == '\n') {
sbfound = true;
break;
}
splitbyte++;
}
splitbyte++;
// Write the part of body already read to ByteArrayOutputStream f
ByteArrayOutputStream f = new ByteArrayOutputStream();
if (splitbyte < rlen) f.write(buf, splitbyte, rlen-splitbyte);
// While Firefox sends on the first read all the data fitting
// our buffer, Chrome and Opera sends only the headers even if
// there is data for the body. So we do some magic here to find
// out whether we have already consumed part of body, if we
// have reached the end of the data to be sent or we should
// expect the first byte of the body at the next read.
if (splitbyte < rlen)
size -= rlen - splitbyte +1;
else if (!sbfound || size == 0x7FFFFFFFFFFFFFFFl)
size = 0;
// Now read all the body and write it to f
buf = new byte[512];
while ( rlen >= 0 && size > 0 )
{
rlen = is.read(buf, 0, 512);
size -= rlen;
if (rlen > 0)
f.write(buf, 0, rlen);
}
// Get the raw body as a byte []
byte [] fbuf = f.toByteArray();
// Create a BufferedReader for easily reading it as string.
ByteArrayInputStream bin = new ByteArrayInputStream(fbuf);
BufferedReader in = new BufferedReader( new InputStreamReader(bin));
// If the method is POST, there may be parameters
// in data section, too, read it:
if ( method.equalsIgnoreCase( "POST" ))
{
String contentType = "";
String contentTypeHeader = header.getProperty("content-type");
StringTokenizer st = new StringTokenizer( contentTypeHeader , "; " );
if ( st.hasMoreTokens()) {
contentType = st.nextToken();
}
if (contentType.equalsIgnoreCase("multipart/form-data"))
{
// Handle multipart/form-data
if ( !st.hasMoreTokens())
sendError( HTTP_BADREQUEST, "BAD REQUEST: Content type is multipart/form-data but boundary missing. Usage: GET /example/file.html" );
String boundaryExp = st.nextToken();
st = new StringTokenizer( boundaryExp , "=" );
if (st.countTokens() != 2)
sendError( HTTP_BADREQUEST, "BAD REQUEST: Content type is multipart/form-data but boundary syntax error. Usage: GET /example/file.html" );
st.nextToken();
String boundary = st.nextToken();
decodeMultipartData(boundary, fbuf, in, parms, files);
}
else
{
// Handle application/x-www-form-urlencoded
String postLine = "";
char pbuf[] = new char[512];
int read = in.read(pbuf);
while ( read >= 0 && !postLine.endsWith("\r\n") )
{
postLine += String.valueOf(pbuf, 0, read);
read = in.read(pbuf);
}
postLine = postLine.trim();
decodeParms( postLine, parms );
}
}
// Ok, now do the serve()
Response r = serve( uri, method, header, parms, files );
if ( r == null )
sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: Serve() returned a null response." );
else
sendResponse( r.status, r.mimeType, r.header, r.data, r.isStreaming );
in.close();
is.close();
// Ok, finish this http request
serveDone(r);
}
catch ( IOException ioe )
{
try
{
sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
}
catch ( Throwable t ) {}
}
catch ( InterruptedException ie )
{
// Thrown by sendError, ignore and exit the thread.
}
}
// Ok, now do the serve()
Response r = serve( uri, method, header, parms, files );
if ( r == null )
sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: Serve() returned a null response." );
else
sendResponse( r.status, r.mimeType, r.header, r.data, r.isStreaming );
sendResponse返回Response信息。
private void sendResponse( String status, String mime, Properties header, InputStream data , boolean isStreaming)
{
try
{
if ( status == null )
throw new Error( "sendResponse(): Status can't be null." );
OutputStream out = mySocket.getOutputStream();
PrintWriter pw = new PrintWriter( out );
pw.print("HTTP/1.0 " + status + " \r\n");
if ( mime != null )
pw.print("Content-Type: " + mime + "\r\n");
if ( header == null || header.getProperty( "Date" ) == null )
pw.print( "Date: " + gmtFrmt.format( new Date()) + "\r\n");
if ( header != null )
{
Enumeration e = header.keys();
while ( e.hasMoreElements())
{
String key = (String)e.nextElement();
String value = header.getProperty( key );
pw.print( key + ": " + value + "\r\n");
}
}
pw.print("\r\n");
pw.flush();
if ( data != null )
{
if ( isStreaming == false) {
int pending = data.available(); // This is to support partial sends, see serveFile()
byte[] buff = new byte[2048];
while (pending>0)
{
int read = data.read( buff, 0, ( (pending>2048) ? 2048 : pending ));
if (read <= 0) break;
out.write( buff, 0, read );
pending -= read;
}
} else {
byte[] buff = new byte[1024*16];
while (true)
{
int read = data.read( buff, 0, 1024*16);
if (read < 0)
break;
if (read > 0)
out.write( buff, 0, read );
}
}
}
out.flush();
out.close();
if ( data != null )
data.close();
}
catch( IOException ioe )
{
// Couldn't write? No can do.
try { mySocket.close(); } catch( Throwable t ) {}
}
}
private Socket mySocket;
}