当前位置: 首页 > 工具软件 > NanoHTTPD > 使用案例 >

NanoHTTPD学习

李建中
2023-12-01

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();
	}

首先,根据端口和目录构建一个ServerSocket,开启一个线程,无限循环执行HTTPSession,HTTPSession是一会话,类似于Apache中的Session。

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.
			}
            
	    }

HTTPSession开启线程,对请求的数据以及头信息进行解析,将头信息和内容分离,根据请求信息,构建返回内容。

				// 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 );

server函数的对用是构建Response,其实对外公开API,即可以重写此函数,从而可以重新构造Response信息。

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

 类似资料: