分享web开发知识

注册/登录|最近发布|今日推荐

主页 IT知识网页技术软件开发前端开发代码编程运营维护技术分享教程案例
当前位置:首页 > 教程案例

【转】如何开发自己的HttpServer-NanoHttpd源码解读

发布时间:2023-09-06 01:11责任编辑:苏小强关键词:暂无标签

现在作为一个开发人员,http server相关的内容已经是无论如何都要了解的知识了。用curl发一个请求,配置一下apache,部署一个web server对我们来说都不是很难,但要想搞清楚这些背后都发生了什么技术细节还真不是很简单的。所以新的系列将是分享我学习Http Server的过程。

NanoHttpd是Github上的一个开源项目,号称只用一个java文件就能创建一个http server,我将通过分析NanoHttpd的源码解析如何开发自己的HttpServer。Github 地址:https://github.com/NanoHttpd/nanohttpd

在开始前首先简单说明HttpServer的基本要素:

1.能接受HttpRequest并返回HttpResponse

2.满足一个Server的基本特征,能够长时间运行

关于Http协议一般HttpServer都会声明支持Http协议的哪些特性,nanohttpd作为一个轻量级的httpserver只实现了最简单、最常用的功能,不过我们依然可以从中学习很多。

首先看下NanoHttpd类的start函数

[java]view plaincopy
  1. publicvoidstart()throwsIOException{
  2. myServerSocket=newServerSocket();
  3. myServerSocket.bind((hostname!=null)?newInetSocketAddress(hostname,myPort):newInetSocketAddress(myPort));
  4. myThread=newThread(newRunnable(){
  5. @Override
  6. publicvoidrun(){
  7. do{
  8. try{
  9. finalSocketfinalAccept=myServerSocket.accept();
  10. registerConnection(finalAccept);
  11. finalAccept.setSoTimeout(SOCKET_READ_TIMEOUT);
  12. finalInputStreaminputStream=finalAccept.getInputStream();
  13. asyncRunner.exec(newRunnable(){
  14. @Override
  15. publicvoidrun(){
  16. OutputStreamoutputStream=null;
  17. try{
  18. outputStream=finalAccept.getOutputStream();
  19. TempFileManagertempFileManager=tempFileManagerFactory.create();
  20. HTTPSessionsession=newHTTPSession(tempFileManager,inputStream,outputStream,finalAccept.getInetAddress());
  21. while(!finalAccept.isClosed()){
  22. session.execute();
  23. }
  24. }catch(Exceptione){
  25. //Whenthesocketisclosedbytheclient,wethrowourownSocketException
  26. //tobreakthe"keepalive"loopabove.
  27. if(!(einstanceofSocketException&&"NanoHttpdShutdown".equals(e.getMessage()))){
  28. e.printStackTrace();
  29. }
  30. }finally{
  31. safeClose(outputStream);
  32. safeClose(inputStream);
  33. safeClose(finalAccept);
  34. unRegisterConnection(finalAccept);
  35. }
  36. }
  37. });
  38. }catch(IOExceptione){
  39. }
  40. }while(!myServerSocket.isClosed());
  41. }
  42. });
  43. myThread.setDaemon(true);
  44. myThread.setName("NanoHttpdMainListener");
  45. myThread.start();
  46. }

1.创建ServerSocket,bind制定端口

2.创建主线程,主线程负责和client建立连接

3.建立连接后会生成一个runnable对象放入asyncRunner中,asyncRunner.exec会创建一个线程来处理新生成的连接。

4.新线程首先创建了一个HttpSession,然后while(true)的执行httpSession.exec。

这里介绍下HttpSession的概念,HttpSession是java里Session概念的实现,简单来说一个Session就是一次httpClient->httpServer的连接,当连接close后session就结束了,如果没结束则session会一直存在。这点从这里的代码也能看到:如果socket不close或者exec没有抛出异常(异常有可能是client段断开连接)session会一直执行exec方法。

一个HttpSession中存储了一次网络连接中server应该保存的信息,比如:URI,METHOD,PARAMS,HEADERS,COOKIES等。

5.这里accept一个client的socket就创建一个独立线程的server模型是ThreadServer模型,特点是一个connection就会创建一个thread,是比较简单、常见的socket server实现。缺点是在同时处理大量连接时线程切换需要消耗大量的资源,如果有兴趣可以了解更加高效的NIO实现方式。

当获得client的socket后自然要开始处理client发送的httprequest。

Http Request Header的parse:

[plain]view plaincopy
  1. //Readthefirst8192bytes.
  2. //Thefullheadershouldfitinhere.
  3. //Apache‘sdefaultheaderlimitis8KB.
  4. //DoNOTassumethatasinglereadwillgettheentireheaderatonce!
  5. byte[]buf=newbyte[BUFSIZE];
  6. splitbyte=0;
  7. rlen=0;
  8. {
  9. intread=-1;
  10. try{
  11. read=inputStream.read(buf,0,BUFSIZE);
  12. }catch(Exceptione){
  13. safeClose(inputStream);
  14. safeClose(outputStream);
  15. thrownewSocketException("NanoHttpdShutdown");
  16. }
  17. if(read==-1){
  18. //socketwasbeenclosed
  19. safeClose(inputStream);
  20. safeClose(outputStream);
  21. thrownewSocketException("NanoHttpdShutdown");
  22. }
  23. while(read>0){
  24. rlen+=read;
  25. splitbyte=findHeaderEnd(buf,rlen);
  26. if(splitbyte>0)
  27. break;
  28. read=inputStream.read(buf,rlen,BUFSIZE-rlen);
  29. }
  30. }

1.读取socket数据流的前8192个字节,因为http协议中头部最长为8192

2.通过findHeaderEnd函数找到header数据的截止位置,并把位置保存到splitbyte内。

[java]view plaincopy
  1. if(splitbyte<rlen){
  2. inputStream.unread(buf,splitbyte,rlen-splitbyte);
  3. }
  4. parms=newHashMap<String,String>();
  5. if(null==headers){
  6. headers=newHashMap<String,String>();
  7. }
  8. //CreateaBufferedReaderforparsingtheheader.
  9. BufferedReaderhin=newBufferedReader(newInputStreamReader(newByteArrayInputStream(buf,0,rlen)));
  10. //Decodetheheaderintoparmsandheaderjavaproperties
  11. Map<String,String>pre=newHashMap<String,String>();
  12. decodeHeader(hin,pre,parms,headers);

1.使用unread函数将之前读出来的body pushback回去,这里使用了pushbackstream,用法比较巧妙,因为一旦读到了header的尾部就需要进入下面的逻辑来判断是否需要再读下去了,而不应该一直读,读到没有数据为止

2.decodeHeader,将byte的header转换为java对象

[java]view plaincopy
  1. privateintfindHeaderEnd(finalbyte[]buf,intrlen){
  2. intsplitbyte=0;
  3. while(splitbyte+3<rlen){
  4. if(buf[splitbyte]==‘\r‘&&buf[splitbyte+1]==‘\n‘&&buf[splitbyte+2]==‘\r‘&&buf[splitbyte+3]==‘\n‘){
  5. returnsplitbyte+4;
  6. }
  7. splitbyte++;
  8. }
  9. return0;
  10. }

1.http协议规定header和body之间使用两个回车换行分割

[java]view plaincopy
  1. privatevoiddecodeHeader(BufferedReaderin,Map<String,String>pre,Map<String,String>parms,Map<String,String>headers)
  2. throwsResponseException{
  3. try{
  4. //Readtherequestline
  5. StringinLine=in.readLine();
  6. if(inLine==null){
  7. return;
  8. }
  9. StringTokenizerst=newStringTokenizer(inLine);
  10. if(!st.hasMoreTokens()){
  11. thrownewResponseException(Response.Status.BAD_REQUEST,"BADREQUEST:Syntaxerror.Usage:GET/example/file.html");
  12. }
  13. pre.put("method",st.nextToken());
  14. if(!st.hasMoreTokens()){
  15. thrownewResponseException(Response.Status.BAD_REQUEST,"BADREQUEST:MissingURI.Usage:GET/example/file.html");
  16. }
  17. Stringuri=st.nextToken();
  18. //DecodeparametersfromtheURI
  19. intqmi=uri.indexOf(‘?‘);
  20. if(qmi>=0){
  21. decodeParms(uri.substring(qmi+1),parms);
  22. uri=decodePercent(uri.substring(0,qmi));
  23. }else{
  24. uri=decodePercent(uri);
  25. }
  26. //Ifthere‘sanothertoken,it‘sprotocolversion,
  27. //followedbyHTTPheaders.Ignoreversionbutparseheaders.
  28. //NOTE:thisnowforcesheadernameslowercasesincetheyare
  29. //caseinsensitiveandvarybyclient.
  30. if(st.hasMoreTokens()){
  31. Stringline=in.readLine();
  32. while(line!=null&&line.trim().length()>0){
  33. intp=line.indexOf(‘:‘);
  34. if(p>=0)
  35. headers.put(line.substring(0,p).trim().toLowerCase(Locale.US),line.substring(p+1).trim());
  36. line=in.readLine();
  37. }
  38. }
  39. pre.put("uri",uri);
  40. }catch(IOExceptionioe){
  41. thrownewResponseException(Response.Status.INTERNAL_ERROR,"SERVERINTERNALERROR:IOException:"+ioe.getMessage(),ioe);
  42. }
  43. }

1.Http协议第一行是Method URI HTTP_VERSION

2.后面每行都是KEY:VALUE格式的header

3.uri需要经过URIDecode处理后才能使用

4.uri中如果包含?则表示有param,httprequest的param一般表现为:/index.jsp?username=xiaoming&id=2

下面是处理cookie,不过这里cookie的实现较为简单,所以跳过。之后是serve方法,serve方法提供了用户自己实现httpserver具体逻辑的很好接口。在NanoHttpd中的serve方法实现了一个默认的简单处理功能。

[java]view plaincopy
  1. /**
  2. *Overridethistocustomizetheserver.
  3. *<p/>
  4. *<p/>
  5. *(Bydefault,thisdelegatestoserveFile()andallowsdirectorylisting.)
  6. *
  7. *@paramsessionTheHTTPsession
  8. *@returnHTTPresponse,seeclassResponsefordetails
  9. */
  10. publicResponseserve(IHTTPSessionsession){
  11. Map<String,String>files=newHashMap<String,String>();
  12. Methodmethod=session.getMethod();
  13. if(Method.PUT.equals(method)||Method.POST.equals(method)){
  14. try{
  15. session.parseBody(files);
  16. }catch(IOExceptionioe){
  17. returnnewResponse(Response.Status.INTERNAL_ERROR,MIME_PLAINTEXT,"SERVERINTERNALERROR:IOException:"+ioe.getMessage());
  18. }catch(ResponseExceptionre){
  19. returnnewResponse
我的编程学习网——分享web前端后端开发技术知识。 垃圾信息处理邮箱 tousu563@163.com 网站地图
icp备案号 闽ICP备2023006418号-8 不良信息举报平台 互联网安全管理备案 Copyright 2023 www.wodecom.cn All Rights Reserved