前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >25. 应用层HTTP原理(3) —— HTTP Server

25. 应用层HTTP原理(3) —— HTTP Server

作者头像
小雨的分享社区
发布2022-10-26 15:49:20
5800
发布2022-10-26 15:49:20
举报
文章被收录于专栏:小雨的CSDN

根据本专题的上一篇文章所说提到的HTTP响应和HTTP请求的格式(HTTP请求和响应格式文章链接)我们可以书写简单的HTTP Server程序,让服务器上的返回给客户端的返回结果返回至网站中

简单版本

代码如下:

代码语言:javascript
复制
package day0314;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * HTTP服务器(最简单的)
 *
 * 底层要基于TCP来实现,要按照TCP的基本格式来先进行开发
 */
public class HTTPServerV1 {
    private ServerSocket serverSocket = null;

    public HTTPServerV1(int port) throws IOException {
       serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动");
        ExecutorService executorService = Executors.newCachedThreadPool();

        while (true){
            //1.获取连接
            Socket clientSocket = serverSocket.accept();
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    //2.处理连接(采用短连接)
                    process(clientSocket);
                }
            });
        }
    }

    private void process(Socket clientSocket)  {
        try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))){
            //下面的操作要严格按照HTTP协议来进行操作
            //1.读取请求并解析
            //  a)解析首行  三部分使用空格切分
            String firstLine = bufferedReader.readLine();
            String[] firstLineTokens = firstLine.split(" ");//切分
            String method = firstLineTokens[0];
            String url = firstLineTokens[1];
            String version = firstLineTokens[2];
            //  b)解析header  按行读取,然后按照冒号空格来分割字符
            Map<String,String> headers = new HashMap<>();
            String line = "";
            //readLine()读到的一行内容,时会自动去掉换行符的
            while ((line = bufferedReader.readLine()) != null && line.length() !=0){
                //还没读完并且读到的不是空字符串
                String[] headerTokens = line.split(": ");//用冒号空格来切分
                headers.put(headerTokens[0],headerTokens[1]);
            }
            //  c)解析body(暂时先不考虑)
            //请求解析完毕,加上一个日志看看请求的内容是否正确
            System.out.printf("%s %s %s\n",method,url,version);
            for (Map.Entry<String,String> entry : headers.entrySet()){
                System.out.print(entry.getKey() + ": " + entry.getValue()+"\n");
            }
            System.out.println();

            //2.根据请求计算响应
            //假设不管是啥样的请求,都返回一个hello这样的html
            String resp = "";
            if(url.equals("/ok")){
                bufferedWriter.write(version+" 200 OK\n");
                resp = "<h1>hello</h1>";
            }else if (url.equals("/notfound")){
                bufferedWriter.write(version+" 404 Not Found\n");
                resp = "<h1>not found</h1>";
            }else if (url.equals("/seeother")){
                bufferedWriter.write(version+" 303 See Other\n");
                bufferedWriter.write("Location: http://www.baidu.com\n");
            }else {
                bufferedWriter.write(version+" 200 OK\n");
                resp = "<h1>else</h1>";
            }

            //3.把响应写回客户端
            bufferedWriter.write("Content-Type: text/html\n");
            bufferedWriter.write("Content-Length: "+resp.getBytes().length+"\n");//此处的长度这样写是得到的字节的数目
            //resp.length是字符数目
            bufferedWriter.write("\n");
            bufferedWriter.write(resp);
            bufferedWriter.flush();//刷新

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                clientSocket.close();//短连接,一次交互之后服务器客户端主动断开连接
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        HTTPServerV1 serverV1 = new HTTPServerV1(9090);
        serverV1.start();
    }
}

运行结果:

浏览器(客户端)中返回结果是:

服务器显示结果是:

抓包结果为:

完成再浏览器中返回a+b的值

方法一:

代码语言:javascript
复制
package day0314rev;


import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class HttpServerV1 {
    private ServerSocket serverSocket = null;

    public HttpServerV1(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动");
        while (true){
            Socket clientSocket = serverSocket.accept();
            ExecutorService executorService = Executors.newCachedThreadPool();
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    process(clientSocket);
                }
            });
        }
    }

    private void process(Socket clientSocket) {
        try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))){
            //1.解析获得的请求
            //解析首行
            String firstLine = bufferedReader.readLine();
            String[] firstLineToken = firstLine.split(" ");
            String method = firstLineToken[0];
            String url = firstLineToken[1];
            String version = firstLineToken[2];

            Map<String,String> parmenters = new HashMap<>();
            int post = url.indexOf("?");
            if (post != -1){
                //如果url中有?,那么就执行里面的操作
                String parmenter = url.substring(post+1);
                KVStart(parmenter,parmenters);
            }

            //解析header
            Map<String,String> headers = new HashMap<>();
            String line = "";
            while ((line = bufferedReader.readLine()) != null && line.length() != 0){
                String[] headerToken = line.split(": ");
                headers.put(headerToken[0],headerToken[1]);
            }

            System.out.printf("%s %s %s\n", method,url,version);
            for (Map.Entry<String,String> entry : headers.entrySet()){
                System.out.print(entry.getKey()+": "+entry.getValue()+"\n");
            }
            System.out.println();

            //2.计算请求
            String resp = "";
            if (url.equals("/hello")){
                bufferedWriter.write(version+" 200 OK\n");
                resp = "<h1>hello</h1>";
            }else if (url.startsWith("/clar")){
                //计算a+b的值
                bufferedWriter.write(version+" 200 OK\n");
                String aStr = parmenters.get("a");
                String bStr = parmenters.get("b");
                int a = Integer.parseInt(aStr);
                int b = Integer.parseInt(bStr);
                int sum = a+b;
                resp = "<h1>"+sum+"</h1>";

            }else  {
                bufferedWriter.write(version + " 200 OK\n");
                resp = "<h1>defualt</h1>";
            }
            //3.把请求返回给客户端

            bufferedWriter.write("Content-Type: text.html\n");
            bufferedWriter.write("Content-Length: "+resp.getBytes().length+"\n");
            bufferedWriter.write("\n");
            bufferedWriter.write(resp);
            bufferedWriter.flush();

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void KVStart(String parmenter, Map<String, String> parmenters) {
        String[] KVToken = parmenter.split("&");
        for (String KV : KVToken){
            String[] result = KV.split("=");
            parmenters.put(result[0],result[1]);
        }
    }

    public static void main(String[] args) throws IOException {
        HttpServerV1 server = new HttpServerV1(9090);
        server.start();
    }

}

但以上代码在逻辑方面不太清晰,下面用三各类进行整理

第一个类:HttpRequest

代码语言:javascript
复制
package day0314;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

/**
 * 请求:表示一个http请求,并负责解析
 */
public class HttpRequest {
    private String method;
    private String url;
    private String version;
    private Map<String,String> headers = new HashMap<>();
    private Map<String,String> parameters = new HashMap<>();

    //请求的构造逻辑,也是用工厂模式来构造
    //此处的参数就是从socket中获取到的InputStream对象
    //就相当于反序列化
    public static HttpRequest build(InputStream inputStream) throws IOException {
        HttpRequest request = new HttpRequest();
        //此处不能把bufferedReader写道try括号中,一旦写进去就意味着bufferedReader就会被关闭,会影响到clientSocket的状态
        //等到最后正给请求处理完了再统一关闭
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        //此处的build过程就是解析请求的过程
        //1.解析首行
        String firstLine = bufferedReader.readLine();
        String[] firstLineTokens = firstLine.split(" ");
        request.method = firstLineTokens[0];
        request.url = firstLineTokens[1];
        request.version = firstLineTokens[2];

        //2.解析url中的参数
        int pos = request.url.indexOf("?");
        if (pos != -1){
            //pos表示问号的下标
            //看看有没有带问号,如果没有带问号就不用解析了
            //index.html?a=10&b=20
            String parameters = request.url.substring(pos+1);//从i+1开始取
            //切分的最终结果
            parseKV(parameters,request.parameters);
        }

        //3.解析header
        String line = "";
        //readLine()读到的一行内容,时会自动去掉换行符的
        while ((line = bufferedReader.readLine()) != null && line.length() !=0){
            //还没读完并且读到的不是空字符串
            String[] headerTokens = line.split(": ");//用冒号空格来切分
            request.headers.put(headerTokens[0],headerTokens[1]);
        }
        //4.解析body

        return request;
    }

    private static void parseKV(String input, Map<String, String> output) {
        //1.先按照&分成若干组键值对
        String[] kvTokens = input.split("&");
        //2.针对切分结果再分别进行按照=切分,得到了键和值
        for (String kv : kvTokens){
            String[] result = kv.split("=");
            output.put(result[0],result[1]);
        }
    }

    //给这个类构造getter方法,不要搞setter方法,因为请求对象内容应该是从网络上解析来的,用户不应该修改

    public String getMethod() {
        return method;
    }

    public String getUrl() {
        return url;
    }

    public String getVersion() {
        return version;
    }

    public String getHeaders(String key) {
        return headers.get(key);
    }

    public String getParameters(String key) {
        return parameters.get(key);
    }

    @Override
    public String toString() {
        return "HttpRequest{" +
                "method='" + method + '\'' +
                ", url='" + url + '\'' +
                ", version='" + version + '\'' +
                ", headers=" + headers +
                ", parameters=" + parameters +
                '}';
    }
}

第二个类:HttpResponse

代码语言:javascript
复制
package day0314;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * 响应:表示一个http响应,负责构造
 */
public class HttpResponse {
    private String version = "HTTP/1.1";
    private int status; //状态码
    private String message; //状态码描述信息
    private Map<String,String> headers = new HashMap<>();
    private StringBuilder body = new StringBuilder();
    //当代码需要把响应写回客户端的时候,就往这个OutputStream里面写就好了
    private OutputStream outputStream = null;

    //工厂方法
    public static HttpResponse build(OutputStream outputStream){
        HttpResponse response = new HttpResponse();
        response.outputStream = outputStream;
        return response;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public void setHeaders(String key, String value) {
        headers.put(key,value);
    }

    public void writeBody(String content) {
        body.append(content);
    }

   //以上的设置属性操作都是在内存中搞得,还需要一个专门的方法,把这些属性按照HTTP协议,都写到Socket中
    public void flush() throws IOException {
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
        bufferedWriter.write(version+" "+status+ " "+message+"\n");
        headers.put("Content-Length",body.toString().getBytes().length+"");
        for (Map.Entry<String,String> entry : headers.entrySet()){
            bufferedWriter.write(entry.getKey()+": "+entry.getValue()+"\n");
        }
        bufferedWriter.write("\n");
        bufferedWriter.write(body.toString());
        bufferedWriter.flush();
    }
}

第三个类:整理

代码语言:javascript
复制
package day0314;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 主逻辑:HttpRequest和 HttpResponse已经梳理的逻辑,在这里完成调用
 */
public class HttpServerV2 {
    private ServerSocket serverSocket = null;

    public HttpServerV2(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动");
        ExecutorService executorService = Executors.newCachedThreadPool();
        while (true){
            Socket clientSocket = serverSocket.accept();
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    process(clientSocket);
                }
            });
        }
    }

    public void process(Socket clientSocket){
        try {
            //1.读取并解析请求
            HttpRequest request = HttpRequest.build(clientSocket.getInputStream());
            System.out.println("request:"+request);
            HttpResponse response = HttpResponse.build(clientSocket.getOutputStream());
            response.setHeaders("Content-Type","text.html");

            //2.根据请求计算响应
            if (request.getUrl().startsWith("/hello")){
                response.setStatus(200);
                response.setMessage("OK");
                response.writeBody("<h1>hello</h1>");
            }else if (request.getUrl().startsWith("/calc")){
                //根据这个逻辑进行计算
                //先获取到a b两个值
                String aStr = request.getParameters("a");
                String bStr = request.getParameters("b");
                int a = Integer.parseInt(aStr);
                int b = Integer.parseInt(bStr);
                int result = a+b;
                response.setStatus(200);
                response.setMessage("OK");
                response.writeBody("<h1> result = "+result+"</h1>");
            } else if (request.getUrl().startsWith("/cookie")){
                response.setStatus(200);
                response.setMessage("OK");
                response.setHeaders("Set-Cookie","I am cookie");
                response.writeBody("<h1>set cookie</h1>");
            }else{
                response.setStatus(200);
                response.setMessage("OK");
                response.writeBody("<h1>default</h1>");
            }

            //3.吧响应写回客户端
            response.flush();

        } catch (IOException | NullPointerException e) {
            e.printStackTrace();
        }finally {
            try {
                //这个操作可以同时关闭getInputStream和getOutputStream
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        HttpServerV2 serverV2 = new HttpServerV2(9090);
        serverV2.start();

    }
}

这样也可以完成上述任务,完成结果如下:

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-03-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简单版本
  • 完成再浏览器中返回a+b的值
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档