JAVA简易WEB服务器(三)

在上一篇《JAVA简易WEB服务器(二)》中我们完成了对浏览器请求的解析,这一篇我们继续来实现响应浏览器的请求,同样的,我们还是先来看一下服务端响应给浏览器的数据格式

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Accept-Ranges: bytes
ETag: W/"129-1456125361109"
Last-Modified: Mon, 22 Feb 2016 07:16:01 GMT
Content-Type: text/html
Content-Length: 129
Date: Mon, 22 Feb 2016 08:08:32 GMT

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>test</title> </head> <body>this is test page. </body> </html>

只要我们响应的数据满足这个格式,浏览器就可以正常解析了,在解析之前还是先来做一些准备工作。
相应请求时可能会出现异常,所以我们先编写一个异常类,和请求的异常类类似。

package com.gujin.server;

/** * 响应异常 * * @author jianggujin * */
public class HQResponseException extends RuntimeException {
   private static final long serialVersionUID = 1L;

   public HQResponseException()
   {
      super();
   }

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

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

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

不管是请求还是响应都需要对字符串进行操作,所以将字符串操作的方法抽取出来形成一个字符串工具类。

package com.gujin.server.utils;

/** * 字符串工具 * * @author jianggujin * */
public class HQString {
   /** * 判断字符串为空 * * @param s * @return */
   public static boolean isEmpty(String s)
   {
      return s == null || s.length() == 0;
   }

   /** * 判断字符串是否非空 * * @param s * @return */
   public static boolean isNotEmpty(String s)
   {
      return !isEmpty(s);
   }

   /** * 判断字符串是空白字符 * * @param s * @return */
   public static boolean isBlack(String s)
   {
      return isEmpty(s) || s.matches("\\s*");
   }

   /** * 判断字符串是非空白字符F * * @param s * @return */
   public static boolean isNotBlack(String s)
   {
      return !isBlack(s);
   }

   /** * 截取字符串 * * @param s * @param flag * @return */
   public static String subStringBefore(String s, String flag)
   {
      if (isEmpty(s) || isEmpty(flag))
      {
         return s;
      }
      int index = s.indexOf(flag);
      if (index != -1)
      {
         return s.substring(0, index);
      }
      return flag;
   }

   /** * 截取字符串 * * @param s * @param flag * @return */
   public static String subStringAfter(String s, String flag)
   {
      if (isEmpty(s) || isEmpty(flag))
      {
         return s;
      }
      int index = s.indexOf(flag);
      if (index != -1)
      {
         return s.substring(index + flag.length());
      }
      return flag;
   }
}

好了,准备工作已经基本完成了,下面我们来编写响应类

package com.gujin.server;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import com.gujin.server.utils.HQString;

/** * HTTP响应 * * @author jianggujin * */
public class HQResponse {
   /** 缓冲区大小 **/
   private final int BUFSIZE = 512;
   /** 响应时间格式化 **/
   private final String RESPONSE_DATE_TIME = "EEE, dd MMM yyyy HH:mm:ss 'GMT'";
   /** HTTP版本 **/
   private final String HTTP_VERSION = "HTTP/1.1";
   /** 响应时间格式化 **/
   private final SimpleDateFormat RESPONSE_DATE_FORMAT = new SimpleDateFormat(
         RESPONSE_DATE_TIME, Locale.US);
   /** 缓冲输出流 **/
   private ByteArrayOutputStream bufferStream = null;
   /** Socket输出流 **/
   private OutputStream stream = null;
   /** 响应码 **/
   private int statusCode = 200;
   /** 内容类型 **/
   private String contentType;

   /** * 构造方法 * * @param socket * @throws IOException */
   public HQResponse(Socket socket) throws IOException
   {
      bufferStream = new ByteArrayOutputStream(BUFSIZE);
      stream = socket.getOutputStream();
   }

   /** * 向客户端写数据 * * @param data * @throws IOException */
   public void write(byte[] data) throws IOException
   {
      bufferStream.write(data);
   }

   /** * 向客户端写数据 * * @param data * @param start * @param len */
   public void write(byte[] data, int start, int len)
   {
      bufferStream.write(data, start, len);
   }

   /** * 向客户端发送头信息 * * @throws IOException */
   private void writeHeader() throws IOException
   {
      stream.write(MessageFormat.format("{0} {1} {2}\r\n", HTTP_VERSION,
            statusCode, "OK").getBytes());
      stream.write(MessageFormat.format("Date: {0}\r\n",
            RESPONSE_DATE_FORMAT.format(new Date())).getBytes());
      stream.write("Server: HQHttpServer 1.0".getBytes());
      if (HQString.isNotEmpty(contentType))
      {
         stream.write(MessageFormat
               .format("Content-Type: {0}\r\n", contentType).getBytes());
      }
      stream.write(MessageFormat.format("Content-Length: {0}\r\n",
            bufferStream.size()).getBytes());
   }

   /** * 实际响应 * * @throws IOException */
   public void response() throws IOException
   {
      writeHeader();

      // 换一行
      stream.write("\r\n".getBytes());
      bufferStream.writeTo(stream);
      bufferStream.flush();
      stream.flush();
   }

   /** * 获得内容类型 * * @return */
   public String getContentType()
   {
      return contentType;
   }

   /** * 设置内容类型 * * @param contentType */
   public void setContentType(String contentType)
   {
      this.contentType = contentType;
   }
}

最后我们对上一篇博客中的HQHttpServer类中的handleRequest方法进行改造,将浏览器请求的头信息响应给浏览器,完成一次交互,完整代码如下:

package com.gujin.server;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.MessageFormat;
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();
      HQResponse response = new HQResponse(socket);
      response.setContentType("text/plain");

      Iterator<String> iterator = request.getHeaderNames();
      while (iterator.hasNext())
      {
         String name = iterator.next();
         response.write(MessageFormat.format("{0}: {1}", name,
               request.getHeader(name)).getBytes());
      }
      response.response();
      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,我们可以看到浏览器已经可以正常的接收服务端相应的数据了,页面显示内容如下:
ACCEPT-ENCODING: gzip,deflate,sdchHOST: 127.0.0.1USER-AGENT: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36CONNECTION: keep-aliveACCEPT: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8ACCEPT-LANGUAGE: zh-CN,zh;q=0.8

打开浏览器的开发者工具观察服务端响应的数据信息

我们可以看到浏览器接收到的头信心与我们相应的数据是一致的。
到这里,一个最简单的WEB服务器已经完成了与浏览器的一次完成整的交互,接下来我们要继续对其优化。

你可能感兴趣的:(java,server,web服务器)