【杂谈】一个简易的静态网页服务器

前言

  上一篇随笔【杂谈】一个回车下去,浏览器做了什么?讲了浏览器的处理,这里再用一个例子讲解一下,也不算讲解,算是梳理一下服务端处理浏览器请求的过程。当然实际过程要比这复杂多了。下文的例子,其实就是《How Tomcat Works》这本书的第一个例子,感兴趣的可以去看这本书。不过书上的例子有问题,我下文中会提到。

注:此项目不需要用tomcat,纯Java底层代码写就可以了。

概述

程序有三个类HttpServer,Request,Response。

HttpServer  => 负责监听socket连接,创建Request、Response对象

Request => 用于获取请求信息的URI(利用Socket的InputStream),这里URI就是静态网页文件的相对路径

Response => 用于发送响应数据报(利用Request获取请求信息,利用OutputStream写出数据)

程序包图

完整代码

由于贴完整代码都会使篇幅略显过长,所以下面都折叠起来了,看客可以逐个展开查看。

HttpServer.java

package com.wze.ex01.pyrmont;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

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

    private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";

    private boolean shutdown = false;

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

    public void await() {
        ServerSocket serverSocket = null;
        int port = 8080;
        try {
            //之所以要绑定监听的IP地址,是因为一个电脑可能有多个网卡
            serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
        } catch (IOException e) {
            e.printStackTrace();
            //如果绑定失败,那么这个程序也就没有运行下去的必要了。
            System.exit(1);
        }
        while(!shutdown) {
            Socket socket = null;
            InputStream input  = null;
            OutputStream output = null;
            try {
                //接收一个请求,处理完毕后关闭连接
                socket = serverSocket.accept();
                input = socket.getInputStream();
                output = socket.getOutputStream();
                Request request = new Request(input);
                request.parse();
                Response response = new Response(output);
                response.setRequest(request);
                response.sendStaticResource();
                socket.close();
                shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
            } catch (Exception e) {
                e.printStackTrace();
                continue;
            }
        }
    }

}

Request.java

package com.wze.ex01.pyrmont;

import java.io.IOException;
import java.io.InputStream;

public class Request {
    private InputStream input;
    private String uri;

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

    public void parse() {
        //之所以是大小是2048,是因为请求行的大小一般就是2048
        StringBuffer request = new StringBuffer(2048);
        int i;
        byte[] buffer = new byte[2048];
        try {
            i = input.read(buffer); //读入数据到buffer,并返回请求行的实际长度
        } catch (IOException e) {
            e.printStackTrace();
            i = -1;
        }
        for(int j = 0; j < i; j++) {
            request.append((char)buffer[j]);
        }
        System.out.println(request.toString());
        uri = parseUri(request.toString()); //从请求行中把uri取出来
        System.out.println(uri);
    }

    /**
     * 获取请求行中的uri
     *
     * 请求行格式:Method URI Version
     * 用空格做分隔符
     * @param requestString
     * @return
     */
    private String parseUri(String requestString) {
        int index1, index2;
        index1 = requestString.indexOf(‘ ‘);
        if(index1 != -1) {
            index2 = requestString.indexOf(‘ ‘, index1+1);
            System.out.println(index1 + " " + index2);
            if(index2 > index1)
                return requestString.substring(index1 + 1, index2);
        }
        return null;
    }

    public String getUri() {
        return uri;
    }
}

Response.java

package com.wze.ex01.pyrmont;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;

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());
            System.out.println(file);
            if(file.exists()) { //如果文件存在,则读取到缓冲数组,再利用socket的outputstream写出数据
                long contentLength = file.length();
                String successMessage = "HTTP/1.1 200 success\r\n" +
                        "Content-Type:text/html\r\n" +
                        "Content-Length:"+contentLength +"\r\n" +
                        "\r\n";
                output.write(successMessage.getBytes());
                fis = new FileInputStream(file);
                //每次最多读写1024字节,直到全部读完
                int ch = fis.read(bytes, 0, BUFFER_SIZE);
                System.out.println(ch);
                while(ch != -1) {
                    output.write(bytes, 0, ch);
                    ch = fis.read(bytes, 0, BUFFER_SIZE);
                }
            } else {
                String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +
                        "Content-Type:text/html\r\n" +
                        "Content-Length:23\r\n" +
                        "\r\n" +
                        "<h1>File Not Found</h1>";
                output.write(errorMessage.getBytes());
            }
        } catch (Exception e) {
            System.out.println(e.toString());
        } finally {
            if(fis != null)
                fis.close();
        }

    }
}

运行效果

运行HttpServer的主方法,然后在浏览器地址栏键入localhost:8080/index.html,你就可以在浏览器看见网页内容了。到这一步就相当于实现了一个apache服务器。

注意:index.html是你自己创建的,你随便写点内容。我是只在body里面写了hello。

代码解析

Request对象中缓冲大小为什么是2048?

因为大多数浏览器请求行最大长度就是2048字节,所以读取2048字节,里面必然完全包含了请求行的数据。这也是parameter传参长度限制的原因,因为parameter在URI中,而URI又是组成请求行的元素之一。

注:HTTP请求报文的请求行由三部分组成,请求方法,URI,协议版本,且这三个参数用空格隔开。

前面说的例子有问题在哪里?

上面的例子是正常的,不过书本里面少了一部分,那就是响应头的编写,如果没有发送响应头给浏览器,它无法识别发送给它的数据是什么。

Content-Length在上文中起什么作用?

细心的朋友会发现,我在响应头中添加了Content-Length的头信息,指明了文件的长度,也就是字节数。有了这个头信息,浏览器就可以知道什么时候数据接收完成。这跟浏览器的加载提示有关。

怎么让别人也能访问到这个网页?

如果你的电脑有公网IP的话,那你要做的只是把程序跑起来挂着,然后开放端口。开放端口是什么意思?默认情况下,防火墙会为了安全,其他电脑是不能随便访问本机的端口(例外,80端口是默认开启的)。开启的方法就是进入防火墙设置进站规则,开放8080端口。

感悟

  其实涉及到网络通信,底层传递的就是一堆字节,而"协议"从一个角度来说,其实就是双方共同遵守的数据格式,它指明从哪里到哪里的字节数据表示的是什么,应用程序根据这些进行处理。想来,其实这些东西在上《计算机网络》的时候都讲到了,只是当时没有现在这种感觉吧。

原文地址:https://www.cnblogs.com/longfurcat/p/10355514.html

时间: 2024-08-01 23:19:20

【杂谈】一个简易的静态网页服务器的相关文章

从零实现一个跨平台的静态网页服务器

大三要结束了,想写点东西,一是为了积累些技术,二是为了下学期实习做做准备,不然简历上的项目经验真没什么好写的. c/c++的底层是操作系统和各种协议以及内存布局,java的底层是jvm.不想写一般的应用程序,那就从0实现一个跨平台的静态网页服务器吧. 封装跨平台的各个类:有Tcp和Udp的套接字类,Http包的解析和生成类还有一个读取Html文件的类 运行结果还算不错,Windows平台下 服务器启动 处理浏览器请求 浏览器显示 当我们请求一个不存在的网页时 Linux平台下运行结果 浏览器显示

探秘Tomcat——一个简易的Servlet容器

即便再简陋的服务器也是服务器,今天就来循着书本的第二章来看看如何实现一个servlet容器. 背景知识 既然说到servlet容器这个名词,我们首先要了解它到底是什么. servlet 相比你或多或少有所了解.servlet是用java编写的服务器端程序,主要功能在于交互式地浏览和修改数据,生成动态Web内容.狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者. 容器 容器的概念很大,在这里

一个简易JVM的实现

 http://catpad.net/michael/jvm/是一个开源的简易的JVM实现.它实现了大多数的JVM功能,实现简单,适合广大技术爱好者了解JVM的内部工作原理.可以参考<Inside the Java Virtual Machine>by Bill Venners,本书对jvm结构,Java class的定义有详细的讲解. 本文介绍下其中的一些关键实现点: 类加载,class loading 线程和同步, thead,synchronization 代码解释 GC JNI 1

宝塔面板+Fikker+BBR算法+CloudXNS---搭建一个简易的全球CDN缓存节点给网站加速

一.组件简介1)宝塔面板 宝塔面板是一款服务器管理软件,支持windows和linux系统,可以通过Web端轻松管理服务器,提升运维效率.例如:创建管理网站.FTP.数据库,拥有可视化文件管理器,可视化软件管理器,可视化CPU.内存.流量监控图表,计划任务等功能.我们在这里只用到它的LNMP/LAMP一键安装功能. linux(centos)版:yum install -y wget && wget -O install.sh http://download.bt.cn/install/i

如何搭建一个简易的Web框架

Web框架本质 什么是Web框架, 如何自己搭建一个简易的Web框架?其实, 只要了解了HTTP协议, 这些问题将引刃而解. 简单的理解:  所有的Web应用本质上就是一个socket服务端, 而用户的浏览器就是一个socket客户端. 用户在浏览器的地址栏输入网址, 敲下回车键便会给服务端发送数据, 这个数据是要遵守统一的规则(格式)的, 这个规则便是HTTP协议. HTTP协议主要规定了客户端和服务器之间的通信格式 浏览器收到的服务器响应的相关信息可以在浏览器调试窗口(F12键开启)的Net

iOS:制作一个简易的计算器

初步接触视图,制作了一个简易的计算器,基本上简单的计算是没有问题的,不是很完美,可能还有一些bug,再接再厉. 1 // 2 // ViewController.m 3 // 计算器 4 // 5 // Created by ma c on 15/8/25. 6 // Copyright (c) 2015年 bjsxt. All rights reserved. 7 // 8 9 #import "ViewController.h" 10 11 @interface ViewContr

Angularjs,WebAPI 搭建一个简易权限管理系统

Angularjs,WebAPI 搭建一个简易权限管理系统 Angularjs名词与概念(一) 1. 目录 前言 Angularjs名词与概念 权限系统原型 权限系统业务 数据库设计和实现 WebAPI项目主体结构 Angularjs前端主体结构 2. 前言 Angularjs开发CRUD类型的Web系统生产力惊人,与jQuery,YUI,kissy,Extjs等前端框架区别非常大,初学者在学习的过程中容易以自己以往的经验来学习Angularjs 往往走入误区,最典型的特征是在的开发过程中,使用

一个做页面静态化的php类

<?phpnamespace Common;/* * * 功能:页面静态化的创建和删除 * 创建:当且仅当,一个页面需要被静态化并且还未静态化时. * 删除:当且仅当,一个页面存在静态化页面并且需要被重新静态化时. * * 作者:郭军周 * * 注 :本类基于ThinkPHP3.2,或者其他具有"单一入口且MVC模式"的其他php框架. * * 使用方式:在Controller的构造方法中获取其对象:在Controller的销毁方法里,用其对象的_static方法. * 例:XX

Socket 初识 用Socket建立一个简易Web服务器

摘自<Asp.Net 本质论>作者:郝冠军 //在.Net中.system.Net命名空间提供了网络编程的大多数数据据类型以及常用操作,其中常用的类型如下:/*IPAddress 类表示一个IP地址* IPEndPoint类用来表示一个IP地址和一个端口号的组合,成为网络的端点.* System.Net.Sockets命名空间中提供了基于Socked编程的数据类型.* Socket类封装了Socked的操作.* 常见的操作:* Listen:设置基于连接通信的Socket进入监听状态,并设置等