精华内容
下载资源
问答
  • 前言作为后端开发人员,在实际的工作中我们会非常高频地使用到web服务器。而tomcat作为web服务器领域中举足轻重的一个web框架,又是不能不学习和了解的。tomcat其实是一个web框架,那么其内部是怎么实现的呢?如果...

    前言

    作为后端开发人员,在实际的工作中我们会非常高频地使用到web服务器。而tomcat作为web服务器领域中举足轻重的一个web框架,又是不能不学习和了解的。

    tomcat其实是一个web框架,那么其内部是怎么实现的呢?如果不用tomcat我们能自己实现一个web服务器吗?

    首先,tomcat内部的实现是非常复杂的,也有非常多的各类组件,我们在后续章节会深入地了解。其次,本章我们将自己实现一个web服务器的。

    http协议简介

    http是一种协议(超文本传输协议),允许web服务器和浏览器通过Internet来发送和接受数据,是一种请求/响应协议。http底层使用TCP来进行通信。目前,http已经迭代到了2.x版本,从最初的0.9、1.0、1.1到现在的2.x,每个迭代都加了很多功能。

    在http中,始终都是客户端发起一个请求,服务器接受到请求之后,然后处理逻辑,处理完成之后再发送响应数据,客户端收到响应数据,然后请求结束。在这个过程中,客户端和服务器都可以对建立的连接进行中断操作。比如可以通过浏览器的停止按钮。

    http协议-请求

    一个http协议的请求包含三部分:

    1. 方法 URI 协议/版本
    2. 请求的头部
    3. 主体内容

    举个例子:

    POST /examples/default.jsp HTTP/1.1Accept: text/plain; text/htmlAccept-Language: en-gbConnection: Keep-AliveHost: localhostUser-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)Content-Length: 33Content-Type: application/x-www-form-urlencodedAccept-Encoding: gzip, deflatelastName=Franks&firstName=Michael

    数据的第一行包括:方法、URI、协议和版本。在这个例子里,方法为POST,URI为/examples/default.jsp,协议为HTTP/1.1,协议版本号为1.1。他们之间通过空格来分离。

    请求头部从第二行开始,使用英文冒号(:)来分离键和值。

    请求头部和主体内容之间通过空行来分离,例子中的请求体为表单数据。

    http协议-响应

    类似于http协议的请求,响应也包含三个部分。

    1. 协议 状态 状态描述
    2. 响应的头部
    3. 主体内容

    举个例子:

    HTTP/1.1 200 OKServer: Microsoft-IIS/4.0Date: Mon, 5 Jan 2004 13:13:33 GMTContent-Type: text/htmlLast-Modified: Mon, 5 Jan 2004 13:13:12 GMTContent-Length: 112HTTP Response Example Welcome to Brainy Software

    第一行,HTTP/1.1 200 OK表示协议、状态和状态描述。

    之后表示响应头部。

    响应头部和主体内容之间使用空行来分离。

    Socket

    Socket,又叫套接字,是网络连接的一个端点(end point)。套接字允许应用程序从网络中读取和写入数据。两个不同计算机的不同进程之间可以通过连接来发送和接受数据。A应用要向B应用发送数据,A应用需要知道B应用所在的IP地址和B应用开放的套接字端口。java里面使用java.net.Socket来表示一个套接字。

    java.net.Socket最常用的一个构造方法为:public Socket(String host, int port);,host表示主机名或ip地址,port表示套接字端口。我们来看一个例子:

    Socket socket = new Socket("127.0.0.1", "8080");OutputStream os = socket.getOutputStream(); boolean autoflush = true;PrintWriter out = new PrintWriter( socket.getOutputStream(), autoflush);BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputstream()));// send an HTTP request to the web server out.println("GET /index.jsp HTTP/1.1"); out.println("Host: localhost:8080"); out.println("Connection: Close");out.println();// read the responseboolean loop = true;StringBuffer sb = new StringBuffer(8096); while (loop) {    if (in.ready()) {         int i=0;        while (i != -1) {            i = in.read();            sb.append((char) i);         }        loop = false;    }    Thread.currentThread().sleep(50L);}

    这儿通过socket.getOutputStream()来发送数据,使用socket.getInputstream()来读取数据。

    ServerSocket

    Socket表示一个客户端套接字,任何时候如果你想发送或接受数据,都需要构造创建一个Socket。现在假如我们需要一个服务器端的应用程序,我们需要额外考虑更多的东西。因为服务器需要随时待命,它不清楚什么时候一个客户端会连接到它。在java里面,我们可以通过java.net.ServerSocket来表示一个服务器套接字。

    ServerSocket和Socket不同,它需要等待来自客户端的连接。一旦有客户端和其建立了连接,ServerSocket需要创建一个Socket来和客户端进行通信。

    ServerSocket有很多的构造方法,我们拿其中的一个来举例子。

    public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException;new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));

    参数如下:

    1. port 表示端口
    2. backlog 表示队列的长度
    3. bindAddr 表示地址

    HttpServer

    HttpServer表示一个服务器端入口,提供了一个main方法,并一直在8080端口等待,直到客户端建立一个连接。这时,服务器通过生成一个Socket来对此连接进行处理。

    public class HttpServer {  /** WEB_ROOT is the directory where our HTML and other files reside.   *  For this package, WEB_ROOT is the "webroot" directory under the working   *  directory.   *  The working directory is the location in the file system   *  from where the java command was invoked.   */  public static final String WEB_ROOT =    System.getProperty("user.dir") + File.separator  + "webroot";  // shutdown command  private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";  // the shutdown command received  private boolean shutdown = false;  public static void main(String[] args) {    HttpServer server = new HttpServer();    server.await();  }  public void await() {    ServerSocket serverSocket = null;    int port = 8080;    try {      serverSocket =  new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));    }    catch (IOException e) {      e.printStackTrace();      System.exit(1);    }    // Loop waiting for a request    while (!shutdown) {      Socket socket = null;      InputStream input = null;      OutputStream output = null;      try {        socket = serverSocket.accept();        input = socket.getInputStream();        output = socket.getOutputStream();        // create Request object and parse        Request request = new Request(input);        request.parse();        // create Response object        Response response = new Response(output);        response.setRequest(request);        response.sendStaticResource();        // Close the socket        socket.close();        //check if the previous URI is a shutdown command        shutdown = request.getUri().equals(SHUTDOWN_COMMAND);      }      catch (Exception e) {        e.printStackTrace();        continue;      }    }  }}

    Request对象主要完成几件事情:

    1. 解析请求数据
    2. 解析uri(请求数据第一行)
    public class Request {  private InputStream input;  private String uri;  public Request(InputStream input) {    this.input = input;  }  public void parse() {    // Read a set of characters from the socket    StringBuffer request = new StringBuffer(2048);    int i;    byte[] buffer = new byte[2048];    try {      i = input.read(buffer);    }    catch (IOException e) {      e.printStackTrace();      i = -1;    }    for (int j=0; j index1)        return requestString.substring(index1 + 1, index2);    }    return null;  }  public String getUri() {    return uri;  }}

    Response主要是向客户端发送文件内容(如果请求的uri指向的文件存在)。

    public class Response {  private static final int BUFFER_SIZE = 1024;  Request request;  OutputStream output;  public Response(OutputStream output) {    this.output = output;  }  public void setRequest(Request request) {    this.request = request;  }  public void sendStaticResource() throws IOException {    byte[] bytes = new byte[BUFFER_SIZE];    FileInputStream fis = null;    try {      File file = new File(HttpServer.WEB_ROOT, request.getUri());      if (file.exists()) {        fis = new FileInputStream(file);        int ch = fis.read(bytes, 0, BUFFER_SIZE);        while (ch!=-1) {          output.write(bytes, 0, ch);          ch = fis.read(bytes, 0, BUFFER_SIZE);        }      }      else {        // file not found        String errorMessage = "HTTP/1.1 404 File Not Found" +          "Content-Type: text/html" +          "Content-Length: 23" +          "" +          "

    File Not Found

    ";        output.write(errorMessage.getBytes());      }    }    catch (Exception e) {      // thrown if cannot instantiate a File object      System.out.println(e.toString() );    }    finally {      if (fis!=null)        fis.close();    }  }}

    总结

    在看了上面的例子之后,我们惊奇地发现,在Java里面实现一个web服务器真容易,代码也非常简单和清晰!

    d88d874da27d4189cd7766b9190cf813.png
    展开全文
  • Node.js是JavaScript基础上发展起来的语言,所以前端开发者应该天生就会一点。一般我们会用它来做CLI工具或者Web服务器,做Web服务器也有很多...本文要讲的就是不借助框架,只用原生API怎么写一个Web服务器。因为...

    Node.jsJavaScript基础上发展起来的语言,所以前端开发者应该天生就会一点。一般我们会用它来做CLI工具或者Web服务器,做Web服务器也有很多成熟的框架,比如ExpressKoa。但是ExpressKoa都是对Node.js原生API的封装,所以其实不借助任何框架,只用原生API我们也能写一个Web服务器出来。本文要讲的就是不借助框架,只用原生API怎么写一个Web服务器。因为在我的计划中,后面会讲ExpressKoa的源码解析,他们都是使用原生API来实现的。所以本文其实是这两个源码解析的前置知识,可以帮我们更好的理解ExpressKoa这种框架的意义和源码。本文仅为说明原生API的使用方法,代码较丑,请不要在实际工作中模仿!

    本文可运行代码示例已经上传GitHub,大家可以拿下来玩玩:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/Node.js/HttpServer

    Hello World

    要搭建一个简单的Web服务器,使用原生的http模块就够了,一个简单的Hello World程序几行代码就够了:

    const http = require('http')
    
    const port = 3000
    
    const server = http.createServer((req, res) => {
      res.statusCode = 200
      res.setHeader('Content-Type', 'text/plain')
      res.end('Hello World')
    })
    
    server.listen(port, () => {
      console.log(`Server is running on http://127.0.0.1:${port}/`)
    })

    这个例子就很简单,直接用http.createServer创建了一个服务器,这个服务器也没啥逻辑,只是在访问的时候返回Hello World。服务器创建后,使用server.listen运行在3000端口就行。

    这个例子确实简单,但是他貌似除了输出一个Hello World之外,啥也干不了,离我们一般使用的Web服务器还差了很远,主要是差了这几块:

    1. 不支持HTTP动词,比如GETPOST
    2. 不支持路由
    3. 没有静态资源托管
    4. 不能持久化数据

    前面三点是一个Web服务器必备的基础功能,第四点是否需要要看情况,毕竟目前很多NodeWeb服务器只是作为一个中间层,真正跟数据库打交道做持久化的还是各种微服务,但是我们也应该知道持久化怎么做。

    所以下面我们来写一个真正能用的Web服务器,也就是说把前面缺的几点都补上。

    处理路由和HTTP动词

    前面我们的那个Hello World也不是完全不能用,因为代码位置还是得在http.createServer里面,我们就在里面添加路由的功能。为了跟后面的静态资源做区分,我们的API请求都以/api开头。要做路由匹配也不难,最简单的就是直接用if条件判断就行。为了能拿到请求地址,我们需要使用url模块来解析传过来的地址。而Http动词直接可以用req.method拿到。所以http.createServer改造如下:

    const url = require('url');
    
    const server = http.createServer((req, res) => {
      // 获取url的各个部分
      // url.parse可以将req.url解析成一个对象
      // 里面包含有pathname和querystring等
      const urlObject = url.parse(req.url);
      const { pathname } = urlObject;
    
      // api开头的是API请求
      if (pathname.startsWith('/api')) {
        // 再判断路由
        if (pathname === '/api/users') {
          // 获取HTTP动词
          const method = req.method;
          if (method === 'GET') {
            // 写一个假数据
            const resData = [
              {
                id: 1,
                name: '小明',
                age: 18
              },
              {
                id: 2,
                name: '小红',
                age: 19
              }
            ];
            res.setHeader('Content-Type', 'application/json')
            res.end(JSON.stringify(resData));
            return;
          }
        }
      }
    });

    现在我们访问/api/users就可以拿到用户列表了:

    e3069822ab31952bec2f1724d296f117.png

    支持静态文件

    上面说了API请求是以/api开头,也就是说不是以这个开头的可以认为都是静态文件,不同文件有不同的Content-Type,我们这个例子里面暂时只支持一种.jpg吧。其实就是给我们的if (pathname.startsWith('/api'))加一个else就行。返回静态文件需要:

    1. 使用fs模块读取文件。
    2. 返回文件的时候根据不同的文件类型设置不同的Content-Type

    所以我们这个else就长这个样子:

    // ... 省略前后代码 ...
    
    else {
      // 使用path模块获取文件后缀名
      const extName = path.extname(pathname);
    
      if (extName === '.jpg') {
        // 使用fs模块读取文件
        fs.readFile(pathname, (err, data) => {
          res.setHeader('Content-Type', 'image/jpeg');
          res.write(data);
          res.end();
        })
      }
    }

    然后我们在同级目录下放一个图片试一下:

    540d8e747ffbaebd553ff6032d98da10.png

    数据持久化

    数据持久化的方式有好几种,一般都是存数据库,少数情况下也有存文件的。存数据库比较麻烦,还需要创建和连接数据库,我们这里不好demo,我们这里演示一个存文件的例子。一般POST请求是用来存新数据的,我们在前面的基础上再添加一个POST /api/users来新增一条数据,只需要在前面的if (method === 'GET')后面加一个POST的判断就行:

    // ... 省略其他代码 ...
    
    else if (method === 'POST') {
      // 注意数据传过来可能有多个chunk
      // 我们需要拼接这些chunk
      let postData = '';
      req.on('data', chunk => {
        postData = postData + chunk;
      })
    
      req.on('end', () => {
        // 数据传完后往db.txt插入内容
        fs.appendFile(path.join(__dirname, 'db.txt'), postData, () => {
          res.end(postData);  // 数据写完后将数据再次返回
        });
      })
    }

    然后我们测试一下这个API:

    288508e2a01b2aacc187b73644a31bc0.png

    再去看看文件里面写进去没有:

    1ec9579c118a5615933a237d749f8774.png

    总结

    到这里我们就完成了一个具有基本功能的web服务器,代码不复杂,但是对于帮我们理解Node web服务器的原理很有帮助。但是上述代码还有个很大的问题就是:代码很丑!所有代码都写在一堆,而且HTTP动词和路由匹配全部是使用if条件判断,如果有几百个API,再配合十来个动词,那代码简直就是个灾难!所以我们应该将路由处理HTTP动词静态文件数据持久化这些功能全部抽离出来,让整个应用变得更优雅,更好扩展。这就是ExpressKoa这些框架存在的意义,下一篇文章我们就去Express的源码看看他是怎么解决这个问题的,点个关注不迷路~

    本文可运行代码示例已经上传GitHub,大家可以拿下来玩玩:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/Node.js/HttpServer

    文章的最后,感谢你花费宝贵的时间阅读本文,如果本文给了你一点点帮助或者启发,请不要吝啬你的赞和GitHub小星星,你的支持是作者持续创作的动力。

    作者博文GitHub项目地址: https://github.com/dennis-jiang/Front-End-Knowledges

    作者掘金文章汇总:https://juejin.im/post/5e3ffc85518825494e2772fd

    我也搞了个公众号[进击的大前端],不打广告,不写水文,只发高质量原创,欢迎关注~

    展开全文
  • Node.js是JavaScript基础上发展起来的语言,所以前端开发者应该天生就会一点。一般我们会用它来做CLI工具或者Web服务器,做Web服务器也有很多成熟的框架,...本文要讲的就是不借助框架,只用原生API怎么写一个Web服...

    Node.jsJavaScript基础上发展起来的语言,所以前端开发者应该天生就会一点。一般我们会用它来做CLI工具或者Web服务器,做Web服务器也有很多成熟的框架,比如ExpressKoa。但是ExpressKoa都是对Node.js原生API的封装,所以其实不借助任何框架,只用原生API我们也能写一个Web服务器出来。本文要讲的就是不借助框架,只用原生API怎么写一个Web服务器。因为在我的计划中,后面会写ExpressKoa的源码解析,他们都是使用原生API来实现的。所以本文其实是这两个源码解析的前置知识,可以帮我们更好的理解ExpressKoa这种框架的意义和源码。本文仅为说明原生API的使用方法,代码较丑,请不要在实际工作中模仿!

    本文可运行代码示例已经上传GitHub,大家可以拿下来玩玩:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/Node.js/HttpServer

    Hello World

    要搭建一个简单的Web服务器,使用原生的http模块就够了,一个简单的Hello World程序几行代码就够了:

    const http = require('http')const port = 3000const server = http.createServer((req, res) => {  res.statusCode = 200  res.setHeader('Content-Type', 'text/plain')  res.end('Hello World')})server.listen(port, () => {  console.log(`Server is running on http://127.0.0.1:${port}/`)})

    这个例子就很简单,直接用http.createServer创建了一个服务器,这个服务器也没啥逻辑,只是在访问的时候返回Hello World。服务器创建后,使用server.listen运行在3000端口就行。

    这个例子确实简单,但是他貌似除了输出一个Hello World之外,啥也干不了,离我们一般使用的Web服务器还差了很远,主要是差了这几块:

    1.不支持HTTP动词,比如GETPOST2.不支持路由3.没有静态资源托管4.不能持久化数据

    前面三点是一个Web服务器必备的基础功能,第四点是否需要要看情况,毕竟目前很多NodeWeb服务器只是作为一个中间层,真正跟数据库打交道做持久化的还是各种微服务,但是我们也应该知道持久化怎么做。

    所以下面我们来写一个真正能用的Web服务器,也就是说把前面缺的几点都补上。

    处理路由和HTTP动词

    前面我们的那个Hello World也不是完全不能用,因为代码位置还是得在http.createServer里面,我们就在里面添加路由的功能。为了跟后面的静态资源做区分,我们的API请求都以/api开头。要做路由匹配也不难,最简单的就是直接用if条件判断就行。为了能拿到请求地址,我们需要使用url模块来解析传过来的地址。而Http动词直接可以用req.method拿到。所以http.createServer改造如下:

    const url = require('url');const server = http.createServer((req, res) => {  // 获取url的各个部分  // url.parse可以将req.url解析成一个对象  // 里面包含有pathname和querystring等  const urlObject = url.parse(req.url);  const { pathname } = urlObject;  // api开头的是API请求  if (pathname.startsWith('/api')) {    // 再判断路由    if (pathname === '/api/users') {      // 获取HTTP动词      const method = req.method;      if (method === 'GET') {        // 写一个假数据        const resData = [          {            id: 1,            name: '小明',            age: 18          },          {            id: 2,            name: '小红',            age: 19          }        ];        res.setHeader('Content-Type', 'application/json')        res.end(JSON.stringify(resData));        return;      }    }  }});

    现在我们访问/api/users就可以拿到用户列表了:

    9d1f667563a4556a6d1d8f907d146201.png

    支持静态文件

    上面说了API请求是以/api开头,也就是说不是以这个开头的可以认为都是静态文件,不同文件有不同的Content-Type,我们这个例子里面暂时只支持一种.jpg吧。其实就是给我们的if (pathname.startsWith('/api'))加一个else就行。返回静态文件需要:

    1.使用fs模块读取文件。2.返回文件的时候根据不同的文件类型设置不同的Content-Type

    所以我们这个else就长这个样子:

    // ... 省略前后代码 ...else {  // 使用path模块获取文件后缀名  const extName = path.extname(pathname);  if (extName === '.jpg') {    // 使用fs模块读取文件    fs.readFile(pathname, (err, data) => {      res.setHeader('Content-Type', 'image/jpeg');      res.write(data);      res.end();    })  }}

    然后我们在同级目录下放一个图片试一下:

    8eb6ec8c978a54ecd15ea036bd28e811.png

    数据持久化

    数据持久化的方式有好几种,一般都是存数据库,少数情况下也有存文件的。存数据库比较麻烦,还需要创建和连接数据库,我们这里不好demo,我们这里演示一个存文件的例子。一般POST请求是用来存新数据的,我们在前面的基础上再添加一个POST /api/users来新增一条数据,只需要在前面的if (method === 'GET')后面加一个POST的判断就行:

    // ... 省略其他代码 ...else if (method === 'POST') {  // 注意数据传过来可能有多个chunk  // 我们需要拼接这些chunk  let postData = '';  req.on('data', chunk => {    postData = postData + chunk;  })  req.on('end', () => {    // 数据传完后往db.txt插入内容    fs.appendFile(path.join(__dirname, 'db.txt'), postData, () => {      res.end(postData);  // 数据写完后将数据再次返回    });  })}

    然后我们测试一下这个API:

    add2a647882ede503e13da84004e84c6.png

    再去看看文件里面写进去没有:

    2f58a1756887a8129ebb67912bb09f2d.png

    总结

    到这里我们就完成了一个具有基本功能的web服务器,代码不复杂,但是对于帮我们理解Node web服务器的原理很有帮助。但是上述代码还有个很大的问题就是:代码很丑!所有代码都写在一堆,而且HTTP动词和路由匹配全部是使用if条件判断,如果有几百个API,再配合十来个动词,那代码简直就是个灾难!所以我们应该将路由处理HTTP动词静态文件数据持久化这些功能全部抽离出来,让整个应用变得更优雅,更好扩展。这就是ExpressKoa这些框架存在的意义,下一篇文章我们就去Express的源码看看他是怎么解决这个问题的,点个关注不迷路~

    本文可运行代码示例已经上传GitHub,大家可以拿下来玩玩:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/Node.js/HttpServer

    文章的最后,感谢你花费宝贵的时间阅读本文,如果本文给了你一点点帮助或者启发,请不要吝啬你的赞和GitHub小星星,你的支持是作者持续创作的动力。

    作者博文GitHub项目地址:https://github.com/dennis-jiang/Front-End-Knowledges

    作者掘金文章汇总:https://juejin.im/post/5e3ffc85518825494e2772fd

    我也搞了个公众号[进击的大前端],不打广告,不写水文,只发高质量原创,欢迎关注~

    展开全文
  • 前言 作为后端开发人员,在实际的工作中我们会非常高频地使用到web服务器。而tomcat作为web服务器领域中举足轻重的一个web框架,又是不能不学习和了解的。tomcat其实是一个web框架,那么其内部是怎么实现的呢?如果...

    前言

    作为后端开发人员,在实际的工作中我们会非常高频地使用到web服务器。而tomcat作为web服务器领域中举足轻重的一个web框架,又是不能不学习和了解的。

    tomcat其实是一个web框架,那么其内部是怎么实现的呢?如果不用tomcat我们能自己实现一个web服务器吗?

    首先,tomcat内部的实现是非常复杂的,也有非常多的各类组件,我们在后续章节会深入地了解。其次,本章我们将自己实现一个web服务器的。

    http协议简介

    http是一种协议(超文本传输协议),允许web服务器和浏览器通过Internet来发送和接受数据,是一种请求/响应协议。http底层使用TCP来进行通信。目前,http已经迭代到了2.x版本,从最初的0.9、1.0、1.1到现在的2.x,每个迭代都加了很多功能。

    在http中,始终都是客户端发起一个请求,服务器接受到请求之后,然后处理逻辑,处理完成之后再发送响应数据,客户端收到响应数据,然后请求结束。在这个过程中,客户端和服务器都可以对建立的连接进行中断操作。比如可以通过浏览器的停止按钮。

    http协议-请求

    一个http协议的请求包含三部分:

    1. 方法 URI 协议/版本
    2. 请求的头部
    3. 主体内容

    举个例子:

    POST /examples/default.jsp HTTP/1.1
    Accept: text/plain; text/html
    Accept-Language: en-gb
    Connection: Keep-Alive
    Host: localhost
    User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)
    Content-Length: 33
    Content-Type: application/x-www-form-urlencoded
    Accept-Encoding: gzip, deflate

    lastName=Franks&firstName=Michael

    数据的第一行包括:方法、URI、协议和版本。在这个例子里,方法为POST,URI为/examples/default.jsp,协议为HTTP/1.1,协议版本号为1.1。他们之间通过空格来分离。

    请求头部从第二行开始,使用英文冒号(:)来分离键和值。

    请求头部和主体内容之间通过空行来分离,例子中的请求体为表单数据。

    http协议-响应

    类似于http协议的请求,响应也包含三个部分。

    1. 协议 状态 状态描述
    2. 响应的头部
    3. 主体内容

    举个例子:

    HTTP/1.1 200 OK
    Server: Microsoft-IIS/4.0
    Date: Mon, 5 Jan 2004 13:13:33 GMT
    Content-Type: text/html
    Last-Modified: Mon, 5 Jan 2004 13:13:12 GMT
    Content-Length: 112



    HTTP Response Example 

    Welcome to Brainy Software


    第一行,HTTP/1.1 200 OK表示协议、状态和状态描述。

    之后表示响应头部。

    响应头部和主体内容之间使用空行来分离。

    Socket

    Socket,又叫套接字,是网络连接的一个端点(end point)。套接字允许应用程序从网络中读取和写入数据。两个不同计算机的不同进程之间可以通过连接来发送和接受数据。A应用要向B应用发送数据,A应用需要知道B应用所在的IP地址和B应用开放的套接字端口。java里面使用java.net.Socket来表示一个套接字。

    java.net.Socket最常用的一个构造方法为:public Socket(String host, int port);,host表示主机名或ip地址,port表示套接字端口。我们来看一个例子:

    Socket socket = new Socket("127.0.0.1""8080");
    OutputStream os = socket.getOutputStream(); 
    boolean autoflush = true;
    PrintWriter out = new PrintWriter( socket.getOutputStream(), autoflush);
    BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputstream()));

    // send an HTTP request to the web server 
    out.println("GET /index.jsp HTTP/1.1"); 
    out.println("Host: localhost:8080"); 
    out.println("Connection: Close");
    out.println();

    // read the response
    boolean loop = true;
    StringBuffer sb = new StringBuffer(8096); 
    while (loop) {
        if (in.ready()) { 
            int i=0;
            while (i != -1) {
                i = in.read();
                sb.append((char) i); 
            }
            loop = false;
        }
        Thread.currentThread().sleep(50L);
    }

    这儿通过socket.getOutputStream()来发送数据,使用socket.getInputstream()来读取数据。

    ServerSocket

    Socket表示一个客户端套接字,任何时候如果你想发送或接受数据,都需要构造创建一个Socket。现在假如我们需要一个服务器端的应用程序,我们需要额外考虑更多的东西。因为服务器需要随时待命,它不清楚什么时候一个客户端会连接到它。在java里面,我们可以通过java.net.ServerSocket来表示一个服务器套接字。

    ServerSocket和Socket不同,它需要等待来自客户端的连接。一旦有客户端和其建立了连接,ServerSocket需要创建一个Socket来和客户端进行通信。

    ServerSocket有很多的构造方法,我们拿其中的一个来举例子。

    public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException;
    new ServerSocket(80801, InetAddress.getByName("127.0.0.1"));

    参数如下:

    1. port 表示端口
    2. backlog 表示队列的长度
    3. bindAddr 表示地址

    HttpServer

    HttpServer表示一个服务器端入口,提供了一个main方法,并一直在8080端口等待,直到客户端建立一个连接。这时,服务器通过生成一个Socket来对此连接进行处理。

    public class HttpServer {

      /** WEB_ROOT is the directory where our HTML and other files reside.
       *  For this package, WEB_ROOT is the "webroot" directory under the working
       *  directory.
       *  The working directory is the location in the file system
       *  from where the java command was invoked.
       */

      public static final String WEB_ROOT =
        System.getProperty("user.dir") + File.separator  + "webroot";

      // shutdown command
      private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";

      // the shutdown command received
      private boolean shutdown = false;

      public static void main(String[] args) {
        HttpServer server = new HttpServer();
        server.await();
      }

      public void await() {
        ServerSocket serverSocket = null;
        int port = 8080;
        try {
          serverSocket =  new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
        }
        catch (IOException e) {
          e.printStackTrace();
          System.exit(1);
        }

        // Loop waiting for a request
        while (!shutdown) {
          Socket socket = null;
          InputStream input = null;
          OutputStream output = null;
          try {
            socket = serverSocket.accept();
            input = socket.getInputStream();
            output = socket.getOutputStream();

            // create Request object and parse
            Request request = new Request(input);
            request.parse();

            // create Response object
            Response response = new Response(output);
            response.setRequest(request);
            response.sendStaticResource();

            // Close the socket
            socket.close();

            //check if the previous URI is a shutdown command
            shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
          }
          catch (Exception e) {
            e.printStackTrace();
            continue;
          }
        }
      }
    }

    Request对象主要完成几件事情:

    1. 解析请求数据
    2. 解析uri(请求数据第一行)
    public class Request {

      private InputStream input;
      private String uri;

      public Request(InputStream input) {
        this.input = input;
      }

      public void parse() {
        // Read a set of characters from the socket
        StringBuffer request = new StringBuffer(2048);
        int i;
        byte[] buffer = new byte[2048];
        try {
          i = input.read(buffer);
        }
        catch (IOException e) {
          e.printStackTrace();
          i = -1;
        }
        for (int j=0; j      request.append((char) buffer[j]);
        }
        System.out.print(request.toString());
        uri = parseUri(request.toString());
      }

      private String parseUri(String requestString) {
        int index1, index2;
        index1 = requestString.indexOf(' ');
        if (index1 != -1) {
          index2 = requestString.indexOf(' ', index1 + 1);
          if (index2 > index1)
            return requestString.substring(index1 + 1, index2);
        }
        return null;
      }

      public String getUri() {
        return uri;
      }

    }

    Response主要是向客户端发送文件内容(如果请求的uri指向的文件存在)。

    public class Response {

      private static final int BUFFER_SIZE = 1024;
      Request request;
      OutputStream output;

      public Response(OutputStream output) {
        this.output = output;
      }

      public void setRequest(Request request) {
        this.request = request;
      }

      public void sendStaticResource() throws IOException {
        byte[] bytes = new byte[BUFFER_SIZE];
        FileInputStream fis = null;
        try {
          File file = new File(HttpServer.WEB_ROOT, request.getUri());
          if (file.exists()) {
            fis = new FileInputStream(file);
            int ch = fis.read(bytes, 0, BUFFER_SIZE);
            while (ch!=-1) {
              output.write(bytes, 0, ch);
              ch = fis.read(bytes, 0, BUFFER_SIZE);
            }
          }
          else {
            // file not found
            String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +
              "Content-Type: text/html\r\n" +
              "Content-Length: 23\r\n" +
              "\r\n" +
              "

    File Not Found

    ";
            output.write(errorMessage.getBytes());
          }
        }
        catch (Exception e) {
          // thrown if cannot instantiate a File object
          System.out.println(e.toString() );
        }
        finally {
          if (fis!=null)
            fis.close();
        }
      }
    }

    总结

    在看了上面的例子之后,我们惊奇地发现,在Java里面实现一个web服务器真容易,代码也非常简单和清晰!

    题外话: 目前小哈正在个人博客(新搭建的网站,域名就是犬小哈的拼音) www.quanxiaoha.com 上更新《Go语言教程》、《Gin Web框架教程》,毕竟Go自带天然的并发优势,后端的同学还是要学一下的,这个教程系列小哈会一直更新下去,欢迎小伙伴们访问哦~

    END

    38c0ec3204fbc80e82c80e971a43ed60.gif

    有热门推荐?

    1. 扔掉okhttp、httpClient,来试试这款轻量级HTTP客户端神器?

    2. SpringCloud中Zuul网关原理及其配置,看它就够了!

    3. 一个基于 SpringBoot + Mybatis + Vue 的代码生成器

    4. 面试官问:BitMap了解么?在什么场景下用过?碰到过什么问题?

    e1fc5a4041140126c1c523614a6fefa0.gif

    最近面试BAT,整理一份面试资料Java面试BATJ通关手册,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。

    获取方式:点“在看”,关注公众号并回复 Java 领取,更多内容陆续奉上。

    文章有帮助的话,在看,转发吧。

    谢谢支持哟 (*^__^*)

    展开全文
  • 在详细描述下,现在有个C++的WEB服务器,想用cookie做身份验证,前端用js提交的http登录请求,webserver用户名密码校验成功后,生成cookieid,然后设置响应头setcookie.send回客户端,这时客户端跟据接到的响应头分析...
  • 前言 作为后端开发人员,在实际的工作中我们会非常高频地使用到web服务器。而tomcat作为web服务器领域中举足轻重的一个web框架,又是不能不学习和了解的。tomcat其实是一个web框架,那么其内部是怎么实现的呢?如果...
  • 自己一个Web服务器(2)

    千次阅读 2016-04-20 15:23:49
    自己一个Web服务器(1) ...自己一个Web服务器(2) ...自己一个Web服务器(3) ...你怎么在你刚建立的Web服务器上运行一个Django应用,Flask应用和Pyramid应用,如何不做任何改变而适应不同的web架构
  • Node.js是JavaScript基础上发展起来的语言,所以前端开发者应该天生就会一点。一般我们会用它来做CLI工具或者Web服务器,做Web服务器也有很多成熟的框架,...本文要讲的就是不借助框架,只用原生API怎么写一个Web服...
  • Node.js是JavaScript基础上发展起来的语言...本文要讲的就是不借助框架,只用原生API怎么写一个Web服务器。因为在我的计划中,后面会写Express和Koa的源码解析,他们都是使用原生API来实现的。所以本文其实是这两个源码
  • 自己写Web服务器(续)

    2013-04-30 23:33:00
    前几天了篇有关写Web服务器的博文,得不好,多亏园友们的意见,给了我继续探究的动力。这篇就关于上次做的Web服务器做了些更改。  1.支持ASPX页面的访问 多亏了园友的提点,使用了ApplicationHost类,使得...
  • 我就想着python的nio玩过,但还没怎么用java的nio一个项目,于是灵机一动,花了将近两天,了个简单的web服务器。代码方面没有导入其他包,而使用jdk自带工具包。下面看成果图 目录 成果截图 项目结构 实验...
  • 同样作为一个自学几个月的新手,这个问题还是可以解答的:我的工程里是通过jquery的ajax向后台服务器的端口发送get请求和post请求实现的交互。get请求被我拿来做页面的跳转举个例子:我后台服务器设置的/index的...
  • 一个图片服务器,是用webservice好还是用asp.net webapi好?安全性怎么处理,求指导
  • 转自:...我还问了你一个问题,“怎么服务器在同一时间处理多个请求?”在本文中你将找到答案。那么,系好安全带加大马力。你马上就乘上快车啦。准备好Linux、Mac OS X(或任何类unix系统)和...
  • 作为后端开发人员,在实际的工作中我们会非常高频地使用到web服务器。而tomcat作为web服务器领域中举足轻重的一个web框架,又是不能不学习和了解的。 tomcat其实是一个web框架,那么其内部是怎么实现的呢?如果...
  • http://www.cnblogs.com/WilliamJiang/archive/2012/04/29/2475883.html1.朋友的一个需求,让我给他实现...客户端是手机应用程序,因为没学过Android,所以我只是了一个Java的Demo用来上传文件。服务端:public p...
  • 文章来源:Python之禅作者:江湖十年Web系列文章1、第一章:整天CRUD没劲,了个Web服务器2、Python 撸一个 Web 服务器-第2章:Hello World3、用 Python 撸一个 Web 服务器-第3章:使用 MVC 构建程序4、用 Python...
  • app上传图片过来,服务器保存在tomcat。。。有没有可以参考的例子
  • 服务器通过html调用本地applet读取 applet 的·文件 codebase code 怎么写 假如applet的路径是c:/2222/test.jar 怎么写 麻烦大神看下
  • 1:服务器端的地址 (服务器端的相对地址指的是相对于你的web应用的...web应用中的路径怎么写? 读取web中的文件用context读取,路径要写相对于web应用的路径必须加’/’ 严禁用绝对路径,通通以”/”开头 原则: ...
  • python实现web服务器

    2016-02-02 07:43:46
    刚好看了一下python,就想着用python实现一下web服务器的过程。这个很简单,目前只支持静态文件的加载,动态语言就要接入fastcgi了(目前还在看fastcgi,下一版本更新吧)。以前没过python也是边边...
  • linux web服务器

    2007-01-13 16:11:58
    这是我今天的,关于linux web服务器的配置,呵呵,的有些乱,也不怎么好,适合我这种菜鸟看。 转载于:https://blog.51cto.com/89184/15166
  • fYS香格里拉注册-香格里拉注册登录|网站分类目录最近由于电脑出了问题不得不重新安装需要的文件,代码什么的都没了,以前也没怎么写过东西这回就先试试手,写的不是太好apache php,希望大家不要介意哈。fYS香格里拉...
  • 最近在做一个项目中,需要将原来在...整理下思路,我想到以下的实施方法:1,首先在Web服务器中,新个shell程序,在shell中调用java class。2,在java class中调用serviceFactory生产的service class,得到DB用的s...
  • WEB服务器 是什么?

    千次阅读 2014-03-29 21:05:19
    那么这个WEB服务器到底是怎么回事?它在处理请求时肯定会去调用一些业务逻辑,处理完请求后肯定需要把结果格式化为html形式,然后返回给客户端的浏览器。  在理解 web 的服务端的时候,下面的文字来自百度百科。...
  • 一、概述在Python中,WSGI(Web Server Gateway Interface)定义了Web服务器与Web应用(或Web框架)之间的标准接口。在WSGI的规范下,各种各样的Web服务器和Web框架都可以很好的交互。由于WSGI的存在,用Python一个...
  • 一、概述在Python中,WSGI(Web Server Gateway Interface)定义了Web服务器与Web应用(或Web框架)之间的标准接口。在WSGI的规范下,各种各样的Web服务器和Web框架都可以很好的交互。由于WSGI的存在,用Python一个...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,277
精华内容 510
关键字:

web服务器怎么写