前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >第12次文章:网络编程——httpserver服务器的搭建

第12次文章:网络编程——httpserver服务器的搭建

作者头像
鹏-程-万-里
发布2019-09-27 12:18:53
5100
发布2019-09-27 12:18:53
举报

这周的任务太多了,服务器只学习了一半,先更新出来吧!下周继续!fighting!

这周的主要的学习内容是httpserver服务器的搭建,需要一些简单的HTML语言语法的了解。

一、HTML语法简介:

下面是一个网页的源码:

<!doctype html>
<html lang="en">
 <head>
  <meta charset="UTF-8">
  <meta name="Generator" content="EditPlus®">
  <meta name="Author" content="">
  <meta name="Keywords" content="">
  <meta name="Description" content="">
  <title>第一个表单</title>
 </head>
 <body>
  <pre>
  method:请求方式 get/post
    get:默认方式,数据量小,安全性不高
    post:量大,安全性相对高
  action:请求的服务器路径
  id:编号,前端区分唯一性,js中使用
  name:名称,后端(服务器)区分唯一性,获取值
  只要提交数据给后台,必须存在name
  </pre>
  <form method="get" action="http://localhost:8888/index.html">
  用户名:<input type = "text" name="uname" id="uname"/>
  密码:<input type="password" name="pwd" id="pwd"/>
  兴趣:<input type="checkbox" name="fav" value="0">篮球
  <input type="checkbox" name="fav" value="1">足球
  <input type="checkbox" name="fav" value="2">乒乓球
  <input type="submit" value="登录"/>
  </form>
 </body>
</html>

使用浏览器打开之后,显示的内容如下所示:

下面是两种不同的请求方式,服务器返回的响应信息:

1、post方式:

POST /index.html HTTP/1.1
Host: localhost:8888
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 38
Connection: keep-alive
Upgrade-Insecure-Requests: 1

2、get方式:

GET /index.html?uname=dasdsai&pwd=dsadfa&fav=0&fav=1&fav=2 HTTP/1.1
Host: localhost:8888
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1

二、服务器的封装

http服务器的构建。在网络中的通信过程中,就是客户端和服务器端进行相互请求和应答方式的交互。客户端在自己这边输入服务器地址,请求获取服务器的资源信息。当服务器接收到请求信息的时候,根据已有的资源,进行响应的答复。所以在服务器的搭建过程中,主要就是将请求信息进行封装,并且进行分析,然后根据分析结果,把应答信息发送出去即可。

1、对响应信息进行封装

在对响应信息进行封装的过程中,主要的思想就是利用输出流,按照html语法格式,对应答信息进行包装,然后输出包装之后的信息块。

package com.peng.server.demo01;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Date;

import com.peng.server.util.CloseUtil;

/**
 * 封装响应信息
 *
 */
public class Response {

  //两个常量
  private static String CRLF = "\r\n";//回车换行
  private static String BLANK = " ";//空格
  //流
  BufferedWriter bw ;
  //正文
  private StringBuilder content;
  //存储头信息
  private StringBuilder headInfo;
  //存储正文长度,正文长度是字节长度
  private int len = 0;
  
  //构造器
  public Response() {
    headInfo = new StringBuilder();
    content = new StringBuilder();
    len = 0;    
  }
  public Response(Socket client)  {
    this();
    try {
      bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
    } catch (IOException e) {
      headInfo = null;
    }
  }  
  public Response(OutputStream os) {
    this();
    bw = new BufferedWriter(new OutputStreamWriter(os));
  }
  
  /**
   * 构建正文
   */
  public Response print(String info) {
    content.append(info);    
    len += info.getBytes().length;//不断改变正文长度
    return this;
  }
  
  /**
   * 构建正文+回车
   */
  public Response println(String info) {
    content.append(info).append(CRLF);    
    len += (info+CRLF).getBytes().length;//不断改变正文长度
    return this;
  }

  /**
   * 构建响应头
   * 私有方法,仅供该类内部使用
   */
  private void createHeadInfo(int code) {
    
    //1) HTTP协议版本、代码状态、描述
    headInfo.append("HTTP/1.1").append(BLANK).append(code).append(BLANK);
    switch(code) {
    case 200:
      headInfo.append("OK");
      break;
    case 404:
      headInfo.append("NOT FOUND");
      break;
    case 500:
      headInfo.append("SERVER ERROR");
      break;  
    };
    headInfo.append(CRLF);

    //2) 响应头(Response Head)
    headInfo.append("Server:bjsxt Server/0.0.1").append(CRLF);
    headInfo.append("Date:").append(new Date()).append(CRLF);
    headInfo.append("Content-type:text/html;charset=GBK:").append(CRLF);
    //长度:字节长度
    headInfo.append("Content-Length:").append(len).append(CRLF);
    headInfo.append(CRLF);//分隔符

  }
  
  //推送到客户端
  void pushToClient(int code) throws IOException {
    if(null == headInfo) {
      code = 500;
    }
    createHeadInfo(code);
    //头信息+分隔符
    bw.append(headInfo.toString());
    //正文
    bw.append(content.toString());
    bw.flush();

  }
  
  public void close() {
    CloseUtil.closeAll(bw);
  }

}

tips:

(1)、在html信息中,经常使用到换行和空格,所以我们预定义两个静态常量,使用的时候,直接进行调用。

(2)、在描述代码状态的时候,一般采用200代表正常状态,404代表服务器没有寻找到相关资源,500代表服务器报错。这些状态码属于一种常规操作,在编写服务器的时候,尽量使用常规定义,这样的话,在后续检查代码以及修正代码的时候,便于我们自己快速定位错误地方。

2、封装请求信息

package com.peng.server.demo01;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

/**
 * 封装请求信息
 *
 */
public class Request {
  //请求方式
  private String method;
  //请求资源
  private String url;
  //请求参数
  private Map<String,List<String>> parameterMapValues;
  
  //内部
  public static final String CRLF = "\r\n";
  private InputStream is ;
  private String requestInfo;//请求信息
  
  //构造器
  public Request() {
    method = "";
    url = "";
    parameterMapValues = new HashMap<String,List<String>>();
  }
  public Request(InputStream is) {
    this();
    this.is = is;
    try {
      byte[] data = new byte[20480];
      int len;
      len = is.read(data);
      requestInfo = new String(data,0,len);
    } catch (IOException e) {
      return;
    }
    //分析请求信息
    parseRequestInfo();
  }
  
  /**
   * 分析请求信息
   */
  public void parseRequestInfo() {
    if(null==requestInfo || ( requestInfo.trim().equals(" "))) {
      return;
    }
    
    /**
     * ===================================
     * 从信息的首行分解出:请求方式 请求路径 请求参数(get可能存在)
     * 如:GBT/index.html?name123&pwd=5467 HTTP/1.1
     *
     * 如果为post方式,请求参数可能在最后正文中
     * 
     * ===================================
     */
    //接收请求参数
    String paramString ="";
    
    //1、获取请求方式
    String firstLine = requestInfo.substring(0, requestInfo.indexOf(CRLF));
    int idx =requestInfo.indexOf("/");//   /的位置
    this.method = firstLine.substring(0, idx).trim();//获取请求的方式
    String urlStr = firstLine.substring(idx,firstLine.indexOf("HTTP/")).trim();
    if(this.method.equalsIgnoreCase("post")) {
      this.url = urlStr;
      paramString = requestInfo.substring(requestInfo.lastIndexOf(CRLF)).trim();

    }else if(this.method.equalsIgnoreCase("get")) {
      if(urlStr.contains("?")) {//是否存在参数
        String[] urlArray = urlStr.split("\\?");
        this.url = urlArray[0];
        paramString = urlArray[1];//接收请求参数
      }else {
        this.url = urlStr;
      }
    }
    
    //不存在请求参数
    if(paramString.equals("")) {
      return;
    }
    
    //2、将请求参数封装到MAP中
    parseParams(paramString);
  }
  
  /**
   * 将请求封装到MAP中
   * @param paramString
   */
  private void parseParams(String paramString) {
    //分割 将字符串转成数组
    StringTokenizer token = new StringTokenizer(paramString,"&");
    while(token.hasMoreTokens()) {
      String keyValue = token.nextToken();
      String[] keyValues = keyValue.split("=");
      if(keyValues.length==1) {
        keyValues = Arrays.copyOf(keyValues, 2);
        keyValues[1] = null;        
      }
      String key = keyValues[0].trim();
      String value = null==keyValues[1]?null:decode(keyValues[1].trim(),"gbk");
      //转换成Map  分拣
      if(!parameterMapValues.containsKey(key)) {
        parameterMapValues.put(key, new ArrayList<String>());
      }
      List<String> values = parameterMapValues.get(key);
      values.add(value);  
    }
  }
  
  /**
   * 解决中文问题
   * @param value
   * @param code
   * @return
   */
  private String decode(String value,String code) {
    
    try {
      return java.net.URLDecoder.decode(value, code);
    } catch (UnsupportedEncodingException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    return null;
    
  }
  
  /**
   * 根据页面的name获取对应的多个值
   * @param name
   * @return
   */
  public String[] getParameterValues(String name) {
    List<String> values = null;
    if((values=parameterMapValues.get(name))==null) {
      return null;
    }else {
      return values.toArray(new String[0]);
    }

  }
  
  /**
   * 根据页面的name获取对应的单个值
   * @param name
   * @return
   */
  public String getParameter(String name) {
    String[] values = getParameterValues(name);
    if(null==values) {
      return null;
    }
    return values[0];
  }
  
  //获取Url的方法
  public String getUrl() {
    return url;
  }
  
}

tips:

(1)在进行获取相关值的时候,我们将其显示在网页上,将会涉及到不同编码集和解码集的转换。所以,为了避免解码集和编码集不同而导致的乱码问题,我们自定义一个指定相同解码集与编码集的方法。使用此方法,避免我们在显示时出现的乱码问题。

(2)在管理用户名和参数的问题上时,由于一个用户名可以对应有多个参数,比如密码,喜好等等,所以我们使用了MAP进行存储管理用户信息。键值对,键只有一个,我们使用用户名作为键,然后多个参数,我们使用链表进行存储,这样就可以很好的解决一个键对应多个值的问题。

3、服务器构建

package com.peng.server.demo01;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
 * 创建服务器并启动
 * 请求并响应
 * @author 赵晓鹏
 *
 */
public class Server4 {

  private ServerSocket server;
  private static String CRLF = "\r\n";//回车换行
  private static String BLANK = " ";//空格

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

  }

  /**
   * 启动方法
   */
  public void start() {
    try {
      server = new ServerSocket(8888);
      this.receive();
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

  }

  /**
   * 接收客户端
   */
  private void receive() {
    try {
      Socket client = server.accept();

      //请求
      Request req = new Request(client.getInputStream());

      //响应
      Response rep = new Response(client.getOutputStream());
      //响应信息的构建
      rep.println("<html><head><title>HTTP响应示例</title>");
      rep.println("</head><body>");
      rep.println("欢迎").println(req.getParameter("uname")).println("回来");
      rep.println("</body></html>");
      rep.pushToClient(200);

    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

  }
}

tips:经过相应的封装后,我们在建立服务器的时候,只需要进行建立响应和请求类别就可以了,大大简化服务器建立时的代码。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-03-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java小白成长之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档