JAVA简易WEB服务器(二)

在上一篇博客中《JAVA简易WEB服务器(一)》我们了解了浏览器与服务端之间的通信数据的格式。这一篇博客开始,我们会一步一步的完成一个简易的WEB服务器的开发,需要注意的是,这里介绍的只是一种思路,毕竟开发一个服务器的工作量是很大的,而且需要考虑的事情很多,这里面我们只是简单的实现其部分功能,有兴趣可以自己进行扩展,深入研究。

言归正传,这一篇博客我们需要实现的是对浏览器请求的解析。
首先,为了我们调试的方便,我们先来创建一个日志接口,用于输出日志内容:

package com.gujin.server;

import java.util.logging.Logger;

/**
 * 日志
 *
 * @author jianggujin
 *
 */
public interface HQHttpServerLog
{
   /**
    * 日志对象
    */
   public Logger LOG = Logger.getLogger(HQHttpServerLog.class.getName());
}

我们知道,浏览器请求的方法有POST、GET、PUT等等,这里我们将其抽取出来,用枚举表示:

package com.gujin.server;

/**
 * 请求方法
 *
 * @author jianggujin
 *
 */
public enum HQMethod
{
   /** GET **/
   GET,
   /** POST **/
   POST
}

在进行网络通信的时候,我们需要对套接字等进行关闭、释放的操作,所以我们可以为其编写一个工具类,用于执行其关闭方法:

package com.gujin.server.utils;

import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.logging.Level;

import com.gujin.server.HQHttpServerLog;

/**
 * 关闭工具
 *
 * @author jianggujin
 *
 */
public class HQClose implements HQHttpServerLog
{
   /**
    * 安全关闭
    *
    * @param closeable
    */
   public static final void safeClose(Object closeable)
   {
      try
      {
         if (closeable != null)
         {
            if (closeable instanceof Closeable)
            {
               ((Closeable) closeable).close();
            }
            else if (closeable instanceof Socket)
            {
               ((Socket) closeable).close();
            }
            else if (closeable instanceof ServerSocket)
            {
               ((ServerSocket) closeable).close();
            }
            else
            {
               Class<?> clazz = closeable.getClass().getClass();
               Method method;
               try
               {
                  method = clazz.getMethod("close");
                  try
                  {
                     method.invoke(closeable);
                     return;
                  }
                  catch (Exception e)
                  {
                     throw new RuntimeException(e);
                  }
               }
               catch (Exception e)
               {
               }
               throw new IllegalArgumentException("Unknown object to close");
            }
         }
      }
      catch (IOException e)
      {
         LOG.log(Level.SEVERE, "Could not close", e);
      }
   }
}

好了,我们的准备工作已经完成,后续需要的话,我们再继续添加,现在我们来完成对请求数据的解析工作,我们再来看一下请求的数据格式:

POST /?test=123 HTTP/1.1
Host: 127.0.0.1
Connection: keep-alive
Content-Length: 8
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: null
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8

name=bob

我们要解析请求的数据,那么我们需要解决的问题就是什么时候内容结束,通过观察GET方式和POST方式请求的数据我们可以知道,当浏览器以GET方式进行请求时,最后一行头信息会紧跟上两个\r\n,这个标记就代表了数据结束,当为POST方式请求数据时,在头信息中会有Content-Length信息,代表请求内容的长度,当我们解析到一个空行后再继续读取相应长度的数据即为客户端请求的完整数据。

下面,我们来编写请求的解析类,并为其提供一些公用的方法:

package com.gujin.server;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * HTTP请求
 *
 * @author jianggujin
 *
 */
public class HQRequest
{
   /** 缓冲区大小 **/
   private final int BUFSIZE = 512;
   /** 字节输出流 **/
   private ByteArrayOutputStream outputStream = null;
   /** Socket输入流 **/
   private InputStream stream = null;
   /** 请求方法 **/
   private HQMethod method;
   /** 请求路径 **/
   private String url;
   /** 查询字符串 **/
   private String queryString;
   /** HTTP版本 **/
   private String version;
   /** 请求头 **/
   private Map<String, List<String>> headers = null;
   /** 请求参数 **/
   private Map<String, List<String>> params = null;
   /** 请求编码 **/
   private String charset = null;
   /** 套接字 **/
   private Socket socket = null;

   /**
    * 构造方法
    *
    * @param socket
    * @throws IOException
    */
   public HQRequest(Socket socket) throws IOException
   {
      this.socket = socket;
      outputStream = new ByteArrayOutputStream(512);
      headers = new HashMap<String, List<String>>();
      params = new HashMap<String, List<String>>();
      this.stream = socket.getInputStream();
   }

   /**
    * 执行解析
    *
    * @throws IOException
    */
   public void execute() throws IOException
   {
      parseFirstLine();
      parseHeaders();
      parseBody();
   }

   /**
    * 解析请求第一行,请求方法、请求地址、HTTP版本
    *
    * @throws IOException
    */
   private void parseFirstLine() throws IOException
   {
      String line = readLine();
      try
      {
         int index = line.indexOf(' ');
         int lastIndex = line.lastIndexOf(' ');
         this.method = HQMethod.valueOf(line.substring(0, index).toUpperCase());
         String fullUrl = line.substring(index + 1, lastIndex);
         int tmp = fullUrl.indexOf('?');
         if (tmp > 0)
         {
            this.url = fullUrl.substring(0, tmp);
            this.queryString = fullUrl.substring(tmp + 1);
            dealParamString(queryString);
         }
         else
         {
            this.url = fullUrl;
         }

         this.version = line.substring(lastIndex + 1).toUpperCase();
      }
      catch (Exception e)
      {
         e.printStackTrace();
         throw new HQRequestException("Request format unqualified.");
      }
   }

   /**
    * 解析请求头
    */
   private void parseHeaders() throws IOException
   {
      String line = null;
      while ((line = readLine()) != null)
      {
         // 分隔符位置
         if (line.length() == 0)
         {
            break;
         }
         addHeader(line);
      }
   }

   /**
    * 添加请求头信息
    *
    * @param line
    */
   private void addHeader(String line)
   {
      int index = line.indexOf(": ");
      String name = line.substring(0, index).toUpperCase();
      String value = line.substring(index + 2);
      List<String> list = this.headers.get(name);
      if (list == null)
      {
         list = new ArrayList<String>(1);
         this.headers.put(name, list);
      }
      list.add(value);
   }

   /**
    * 处理查询参数
    *
    * @param queryString
    */
   private void dealParamString(String queryString)
   {
      if (!isEmpty(queryString))
      {
         String[] params = queryString.split("&");
         if (params != null)
         {
            for (String param : params)
            {
               addParameter(param);
            }
         }
      }
   }

   /**
    * 添加参数
    *
    * @param name
    * @param value
    */
   private void addParameter(String line)
   {
      int index = line.indexOf("=");
      String name = line.substring(0, index);
      String value = line.substring(index + 1);
      List<String> values = params.get(name);
      if (values == null)
      {
         values = new ArrayList<String>();
         params.put(name, values);
      }
      values.add(value);
   }

   /**
    * 是否为空
    *
    * @param msg
    * @return
    */
   private boolean isEmpty(String msg)
   {
      return msg == null || msg.length() == 0;
   }

   /**
    * 获得请求参数
    *
    * @param name
    * @return
    */
   public String getParameter(String name)
   {
      List<String> values = params.get(name);
      if (values != null && !values.isEmpty())
      {
         if (isEmpty(charset))
         {
            return values.get(0);
         }
         else
         {
            try
            {
               return URLDecoder.decode(values.get(0), charset);
            }
            catch (UnsupportedEncodingException e)
            {
            }
         }
      }
      return null;
   }

   /**
    * 获得请求参数名称
    *
    * @return
    */
   public Iterator<String> getParameterNames()
   {
      return params.keySet().iterator();
   }

   /**
    * 获得请求参数对应值
    *
    * @param name
    * @return
    */
   public String[] getParameterValues(String name)
   {
      List<String> values = params.get(name);
      if (values != null)
      {
         if (isEmpty(charset))
         {
            return values.toArray(new String[0]);
         }
         else
         {
            int len = values.size();
            String[] v = new String[len];
            try
            {
               for (int i = 0; i < len; i++)
               {
                  v[i] = URLDecoder.decode(values.get(i), charset);
               }
               return v;
            }
            catch (UnsupportedEncodingException e)
            {
            }
         }
      }
      return null;
   }

   /**
    * 解析请求体
    *
    * @throws IOException
    */
   private void parseBody() throws IOException
   {
      if (HQMethod.POST == method)
      {
         int len = getContentLength() - outputStream.size();
         if (len > 0)
         {
            byte[] buffer = new byte[BUFSIZE];
            long total = 0;
            int readLen = -1;
            do
            {
               long left = len - total;
               if (left <= 0)
               {
                  break;
               }
               if (left >= BUFSIZE)
               {
                  readLen = stream.read(buffer);
               }
               else
               {
                  readLen = stream.read(buffer, 0, (int) left);
               }
               if (readLen < 0)
               {
                  break;
               }
               outputStream.write(buffer, 0, readLen);
               total += readLen;
            } while (total < len);
            outputStream.flush();
            if (isEmpty(charset))
            {
               dealParamString(outputStream.toString());
            }
            else
            {
               dealParamString(outputStream.toString(charset));
            }
         }
      }
   }

   /**
    * 从输入流中读取一行
    *
    * @return
    * @throws IOException
    */
   private String readLine() throws IOException
   {
      String line = null;
      int i = -1;
      while ((i = stream.read()) != -1)
      {
         if (i == '\r')
         {
            outputStream.flush();
            line = outputStream.toString();
            outputStream.reset();
            i = stream.read();
            if (i != '\n')
            {
               outputStream.write(i);
            }
            break;
         }
         else if (i == '\n')
         {
            outputStream.flush();
            line = outputStream.toString();
            outputStream.reset();
            break;
         }
         outputStream.write(i);
      }
      return line;
   }

   /**
    * 获得请求方法
    *
    * @return
    */
   public HQMethod getMethod()
   {
      return method;
   }

   /**
    * 获得请求路径
    *
    * @return
    */
   public String getUrl()
   {
      return url;
   }

   /**
    * 获得HTTP版本
    *
    * @return
    */
   public String getVersion()
   {
      return version;
   }

   /**
    * 获得请求头信息
    *
    * @param name
    * @return
    */
   public String getHeader(String name)
   {
      List<String> values = headers.get(name.toUpperCase());
      if (values != null && !values.isEmpty())
      {
         return values.get(0);
      }
      return null;
   }

   /**
    * 获得请求头名称
    *
    * @return
    */
   public Iterator<String> getHeaderNames()
   {
      return headers.keySet().iterator();
   }

   /**
    * 获得请求头对应值
    *
    * @param name
    * @return
    */
   public List<String> getHeaderValues(String name)
   {
      return headers.get(name);
   }

   /**
    * 获得内容长度
    *
    * @return
    */
   public int getContentLength()
   {
      String contentLength = getHeader("Content-Length");
      if (contentLength != null && contentLength.matches("\\d+"))
      {
         return Integer.parseInt(contentLength);
      }
      return -1;
   }

   /**
    * 获得内容类型
    *
    * @return
    */
   public String getContentType()
   {
      return getHeader("Content-Type");
   }

   /**
    * 获得编码
    *
    * @return
    */
   public String getCharset()
   {
      return charset;
   }

   /**
    * 设置编码
    *
    * @param charset
    */
   public void setCharset(String charset)
   {
      this.charset = charset;
   }

   /**
    * 获得查询字符串
    *
    * @return
    */
   public String getQueryString()
   {
      return queryString;
   }

   /**
    * 获得远程主机地址
    *
    * @return
    */
   public String getRemoteAddr()
   {
      return socket.getInetAddress().getHostAddress();
   }

   /**
    * 获得远程主机名
    *
    * @return
    */
   public String getRemoteHost()
   {
      return socket.getInetAddress().getHostName();
   }

   /**
    * 获得远程主机端口
    *
    * @return
    */
   public int getRemotePort()
   {
      return socket.getPort();
   }

   /**
    * 获得本地主机名称
    *
    * @return
    */
   public String getLocalName()
   {
      return socket.getLocalAddress().getHostName();
   }

   /**
    * 获得本地主机地址
    *
    * @return
    */
   public String getLocalAddr()
   {
      return socket.getLocalAddress().getHostAddress();
   }

   /**
    * 获得本地主机端口
    *
    * @return
    */
   public int getLocalPort()
   {
      return socket.getLocalPort();
   }

   /**
    * 获得输入流
    *
    * @return
    */
   public InputStream getInputStream()
   {
      return new ByteArrayInputStream(outputStream.toByteArray());
   }
}

解析异常的类如下:

package com.gujin.server;

/**
 * 请求异常
 *
 * @author jianggujin
 *
 */
public class HQRequestException extends RuntimeException
{
   private static final long serialVersionUID = 1L;

   public HQRequestException()
   {
      super();
   }

   public HQRequestException(String message)
   {
      super(message);
   }

   public HQRequestException(String message, Throwable cause)
   {
      super(message, cause);
   }

   public HQRequestException(Throwable cause)
   {
      super(cause);
   }
}

现在,我们已经完成了请求解析的实现,有点小激动啊,下面我们来编写Server,用于启动、处理浏览器请求,此处我们接收到请求后进打印出请求的头信息

package com.gujin.server;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Iterator;
import java.util.logging.Level;

import com.gujin.server.utils.HQClose;

/**
 * 服务端
 *
 * @author jianggujin
 *
 */
public class HQHttpServer implements HQHttpServerLog
{
   /** 端口号 **/
   private int port = 80;
   /** 服务套接字 **/
   private ServerSocket serverSocket = null;

   /**
    * 默认构造方法
    */
   public HQHttpServer()
   {
   }

   /**
    * 构造方法
    *
    * @param port
    */
   public HQHttpServer(int port)
   {
      this.port = port;
   }

   /**
    * 启动服务器
    */
   public synchronized void start()
   {
      try
      {
         serverSocket = new ServerSocket(port);
         LOG.info("server init success.");
      }
      catch (IOException e)
      {
         LOG.log(Level.SEVERE, e.getMessage(), e);
      }
      new Thread()
      {
         public void run()
         {
            while (!isStop())
            {
               Socket socket;
               try
               {
                  socket = serverSocket.accept();
                  handleRequest(socket);
               }
               catch (IOException e)
               {
                  LOG.log(Level.SEVERE, e.getMessage(), e);
               }
            }
         };
      }.start();
   }

   /**
    * 处理请求
    *
    * @param socket
    * @throws IOException
    */
   public void handleRequest(Socket socket) throws IOException
   {
      HQRequest request = new HQRequest(socket);
      request.execute();

      Iterator<String> iterator = request.getHeaderNames();
      while (iterator.hasNext())
      {
         String name = iterator.next();
         System.err.printf("%s: %s\n", name, request.getHeader(name));
      }

      socket.close();
   }

   /**
    * 是否停止
    *
    * @return
    */
   public boolean isStop()
   {
      return serverSocket == null || serverSocket.isClosed();
   }

   /**
    * 停止服务器
    */
   public synchronized void stop()
   {
      if (!isStop())
      {
         HQClose.safeClose(serverSocket);
         serverSocket = null;
      }
   }

   public static void main(String[] args)
   {
      new HQHttpServer().start();
   }
}

运行代码,并通过浏览器访问http://127.0.0.1,我们可以看到控制台输出如下信息:

2016-2-23 18:32:19 com.gujin.server.HQHttpServer start
信息: server init success.
ACCEPT-ENCODING: gzip,deflate,sdch
HOST: 127.0.0.1
USER-AGENT: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36
CONNECTION: keep-alive
ACCEPT: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
ACCEPT-LANGUAGE: zh-CN,zh;q=0.8

至此,我们对浏览器的请求的解析已经基本完成了,后面我们会继续对其进行完善。

时间: 2024-08-09 11:33:50

JAVA简易WEB服务器(二)的相关文章

JAVA简易WEB服务器(五)

在之前的几篇博客中,我们的服务器已经具备雏形了,我们还需要继续对其进行优化,在<JAVA简易WEB服务器(三)>中,我们启动服务器的方法如下: /** * 启动服务器 */ public synchronized void start() { try { serverSocket = new ServerSocket(port); LOG.info("server init success."); } catch (IOException e) { LOG.log(Leve

JAVA简易WEB服务器(一)

这一篇博客开始将和大家一起使用JAVA编写一个简易的Web服务器. 众所周知Web服务器与客户端之间的通信是使用HTTP协议的.HTTP是一个客户端和服务器端请求和应答的标准(TCP).因为HTTP协议是基于TCP协议的,所以我将使用JAVA中的Socket完成这个简易的Web服务器.关于HTTP更详细的资料,各位可以查阅相关资料进行了解. 在服务器编写之前,我们还是先来看一下浏览器与服务器之间通信的规则到底如何. 首先,我们是用ServerSocket来模拟一个服务端,通过浏览器访问,查看浏览

JAVA简易WEB服务器(四)

在之前的博客中,我们完成了WEB服务器的请求解析以及响应,达到了最基本的要求,我们都知道WEB服务器在处理请求的时候可以获得客户端提交的参数外,还可以获得Cookie信息,响应的时候也会对Cookie进行修改,当然了,Cookie我们可以通过读取或设置头信息来处理,但是这样会很麻烦,所以我们需要将Cookie的处理抽取出来,方便我们使用.另外,服务器在响应客户端请求时不仅仅包含200状态码,还有可能是404.500等,所以我们需要对HQRequest和HQResponse进行修改并添加Cooki

Java实现简易Web服务器_java

众所周知Web服务器与客户端之间的通信是使用HTTP协议的.HTTP是一个客户端和服务器端请求和应答的标准(TCP).因为HTTP协议是基于TCP协议的,所以我将使用JAVA中的Socket完成这个简易的Web服务器.关于HTTP更详细的资料,各位可以查阅相关资料进行了解. 在服务器编写之前,我们还是先来看一下浏览器与服务器之间通信的规则到底如何. 首先,我们是用ServerSocket来模拟一个服务端,通过浏览器访问,查看浏览器请求的内容: import java.io.BufferedWri

用Java实现Web服务器

一.HTTP协议的作用原理 WWW是以Internet作为传输媒介的一个应用系统,WWW网上最基本的传输单位是Web网页.WWW的工作基于客户机/服务器计算模型,由Web 浏览器(客户机)和Web服务器(服务器)构成,两者之间采用超文本传送协议(HTTP)进行通信.HTTP协议是基于TCP/IP协议之上的协议,是Web浏览器和Web服务器之间的应用层协议,是通用的.无状态的.面向对象的协议.HTTP协议的作用原理包括四个步骤: (1) 连接:Web浏览器与Web服务器建立连接,打开一个称为soc

基于java得web服务器实现[Z]

web|web服务|web服务器 基于Java的Web服务器工作原理1 一个 Web 服务器也被称为 HTTP 服务器,它通过 HTTP 协议与客户端通信.这个客户端通常指的是 Web 浏览器.一个基于 Java 的 Web 服务器用到二个重要的类,java.net.Socket 与 java.net.ServerSocket ,并通过 HTTP 消息通信.因此,本文从讨论 HTTP 与这二个类开始,然后我将解释一个与本文相关的简单的 Web 应用. The Hypertext Transfer

利用Java实现Web服务器

一.HTTP协议的作用原理 HTTP协议的工作原理包括四个步骤: 1.连接:Web浏览器与Web服务器建立连接. 2.请求:Web浏览器通过socket向Web服务器提交请求. 3.应答:Web浏览器提交请求后,通过HTTP传送给Web服务器.Web服务器接到请求后,进行事务处理,处理结果又通过HTTP传回给Web浏览器,从而在Web浏览器上显示出所请求的页面. 4.关系连接:当应答结束后,Web浏览器与Web服务器必须断开,以保证其它Web浏览器能够与Web服务器建立连接. 二.用Java实现

Java的网络编程:用Java实现Web服务器

web|web服务|web服务器|编程|网络 超文本传输协议(HTTP)是位于TCP/IP 协议的应用层,是最广为人知的协议,也是互连网中最核心的协议之一,同样,HTTP 也是基于 C/S 或 B/S 模型实现的.事实上,我们使用的浏览器如Netscape 或IE 是实现HTTP 协议中的客户端,而一些常用的Web 服务器软件如Apache.IIS 和iPlanet Web Server 等是实现HTTP 协议中的服务器端.Web 页由服务端资源定位,传输到浏览器,经过浏览器的解释后,被客户所看

用JAVA编写Web服务器

//WebServer.java 用JAVA编写Web服务器 import java.io.*; import java.net.*; public class WebServer { public static void main(String args[]) { int i=1, PORT=8080; ServerSocket server=null; Socket client=null; try { server=new ServerSocket(PORT); System.out.pr