第 1 章 Web运作原理探析

1.1 Web的概念

Web的特征:

  • 用HTML来表示信息

  • 用统一资源定位技术URL来实现网络上信息的精确定位

  • 用网络应用层协议HTTP来规范浏览器与Web服务器之间的通信过程

1.2 HTML简介

1.3 URL简介

  • 应用层协议://主机IP地址或域名/资源所在路径/文件名

1.4 HTTP协议简介

  • 应用层----HTTP

  • 传输层----TCP

  • 网络层----IP

客户端与服务器端之间的一次信息交换:

  1. 客户端与服务器端建立TCP连接

  2. 客户端发出HTTP请求

  3. 服务器端发回相应的HTTP响应

  4. 客户端接收HTTP响应后,解析HTTP响应

  5. 客户端与服务器端之间的TCP连接关闭

1.4.1 HTTP请求格式

  • 请求方法、URI和HTTP协议的版本

    • 请求方法有:GET、POST、HEAD、PUT、DELETE
    • URI:/资源所在路径/文件名
  • 请求头

  • 请求正文

1.4.2 HTTP响应格式

  • HTTP协议的版本、状态码和描述

状态码:一个3位整数,以1、2、3、4或5开头
1xx:信息提示,表示临时的响应
2xx :响应成功,表示服务器成功地接收客服端请求
3xx:重定向
4xx:客户端错误,表明客户端可能有问题
5xx:服务器错误,表明服务器由于遇到某种错误而不能响应客户端请求

常见的状态代码:
200:响应成功
400:错误请求。客户发送的HTTP请求不正确
404:文件不存在。在服务器上没有客户要求访问的文档
405:服务器不支持客户的请求方式
500:服务器内部错误

  • 响应头

  • 响应正文

1.4.3 正文部分的MIME类型

文件扩展名 MIME类型
.html、.htm text/html
.js application/x-javascript
.json application/json
.jpg、.jpeg application/jpeg
.pdf application/pdf

1.5 用Java套接字创建HTTP客户与服务器程序

HTTPServer

package server;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class HTTPServer {
    public static void main(String args[]) {
        int port;
        ServerSocket serverSocker;

        try {
            port = Integer.parseInt(args[0]);
        } catch (Exception e) {
            System.out.println("port = 8080(默认)");
            port = 8080;
        }

        try {
            serverSocker = new ServerSocket(port);
            System.out.println("服务器正在监听端口:" + serverSocker.getLocalPort());

            while (true) {
                try {
                    final Socket socket = serverSocker.accept();
                    System.out.println("建立了与客户的一个新的TCP连接,该客户的地址为:" + socket.getInetAddress() + ":" + socket.getPort());
                    service(socket);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void service(Socket socket) throws Exception {
        InputStream socketIn = socket.getInputStream();
        Thread.sleep(500);
        int size = socketIn.available();
        byte[] buffer = new byte[size];
        socketIn.read(buffer);
        String request = new String(buffer);
        System.out.println(request);

        String firstLineOfRequest = request.substring(0, request.indexOf("\r\n"));
        String[] parts = firstLineOfRequest.split(" ");
        String uri = parts[1];

        String contentType;
        if (uri.contains("html") || uri.contains("htm")) {
            contentType = "text/html";
        } else if (uri.contains("jpg") || uri.contains("jpeg")) {
            contentType = "image/jpeg";
        } else if (uri.contains("gif")) {
            contentType = "image/gif";
        } else {
            // 字节流类型
            contentType = "application/octet-stream";
        }

        String responseFirstLine = "HTTP/1.1 200 OK\r\n";
        String responseHeader = "Content-Type:" + contentType + "\r\n\r\n";
        InputStream inputStream = HTTPServer.class.getResourceAsStream("root/" + uri);

        OutputStream socketOut = socket.getOutputStream();
        socketOut.write(responseFirstLine.getBytes());
        socketOut.write(responseHeader.getBytes());
        int len = 0;
        buffer = new byte[128];
        while ((len = inputStream.read(buffer)) != -1) {
            socketOut.write(buffer, 0, len);
        }

        Thread.sleep(1000);
        socket.close();
    }
}

HTTPClient

package Client;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class HTTPClient {
    public static void main(String[] args) {
        String uri = "index.htm";
        if (args.length != 0) {
            uri = args[0];

        }
        doGet("localhost", 8080, uri);
    }

    public static void doGet(String host, int port, String uri) {
        Socket socket = null;

        try {
            socket = new Socket(host, port);
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            StringBuffer sb = new StringBuffer("GET " + uri + " HTTP/1.1\r\n");
            sb.append("Accept: */*\r\n");
            sb.append("Accept-Language: zh-cn\r\n");
            sb.append("Accept-Encoding: gzip deflate\r\n");
            sb.append("User-Agent: HTTPClint\r\n");
            sb.append("Host: localhost:8080\r\n");
            sb.append("Connection: Keep-Alive\r\n\r\n");

            OutputStream socketOut = socket.getOutputStream();
            socketOut.write(sb.toString().getBytes());

            Thread.sleep(2000);

            InputStream socketIn = socket.getInputStream();
            int size = socketIn.available();
            byte[] buffer = new byte[size];
            socketIn.read(buffer);
            System.out.println(new String(buffer));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                socket.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

1.5.1 演示异构系统之间用HTTP协议通信

  • 只要HTTP客户程序和服务器程序都遵守HTTP协议,那么即使他们分别用不同的语言编写,或者运行在不同操作系统平台上,彼此也能看的懂对方发送的数据.

1.5.2 演示对网页中的超链接的处理过程

1.5.3 演示对网页中的图片的处理过程

  • 两者都是客户端会再次与服务器端创建连接.

1.6 Web发展历程

1.6.1. 发布静态HTML文档

Web服务器向浏览器发送静态HTML文档的过程

  • 信息主要是文本和图片.

1.6.2. 发布静态多媒体信息

1.6.3. 提供浏览器端与用户的动态交互功能

  • JavaScript等脚本语言

1.6.4. 提供服务器端与用户的动态交互功能

  • Web服务器增加了动态执行程序代码的功能,分两种:
    1. 完全用编程语言写的程序,如CGI程序和用Java编写的Servlet程序.
    2. 嵌入程序代码的HTML文档,如ASP,JSP文档.JSP文档是指嵌入了Java程序代码的HTML文档.

Web服务器动态生产HTML文档的过程

HTTPServer改进,动态功能

package server;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

public class HTTPServer1 {
    private static Map<String, Servlet> servletCache = new HashMap<>();

    public static void main(String args[]) {....}

    /**
     * Web服务器动态执行程序代码,在这里指HTTPServer1在运行时动态加载Servlet接口的实现类,创建它的实例,然后调用它的相关方法.
     * 规定:如果客户请求的URI位于servlet子目录下,就按照Servlet来处理,否则就按照普通静态文件处理.
     */
    public static void service(Socket socket) throws Exception {
        InputStream socketIn = socket.getInputStream();
        Thread.sleep(500);
        int size = socketIn.available();
        byte[] buffer = new byte[size];
        socketIn.read(buffer);
        String request = new String(buffer);
        System.out.println(request);

        String firstLineOfRequest = request.substring(0, request.indexOf("\r\n"));
        String[] parts = firstLineOfRequest.split(" ");
        String uri = parts[1];

        // 如果请求访问Servlet,则动态调用Servlet对象的service()方法
        if (uri.contains("servlet")) {
            // 获得Servlet的名字
            String servletName = null;
            if (uri.contains("?")) {
                servletName = uri.substring(uri.indexOf("servlet/") + 8, uri.indexOf("?"));
            } else {
                servletName = uri.substring(uri.indexOf("servlet/") + 8, uri.length());
            }
            // 尝试从Servlet缓存中获取Servlet对象
            Servlet servlet = servletCache.get(servletName);
            // 如果Servlet缓存中不存在,就创建它,并把它存放在Servlet缓存中
            if (servlet == null) {
                servlet = (Servlet) Class.forName("server." + servletName).newInstance();
                // 先调用Servlet的init()方法
                servlet.init();
                servletCache.put(servletName, servlet);
            }

            // 调用Servlet的sercice()方法
            servlet.service(buffer, socket.getOutputStream());

            Thread.sleep(1000);
            socket.close();
            return;
        }

        String contentType;
        // 与HTTPServer一样
        ...
    }
}

Servet接口

package server;

import java.io.OutputStream;

public interface Servlet {
    /**
     * 初始化方法,当HTTPServer1创建了该接口的一个实例后,就会立即调用该实例的init()方法
     */
    void init() throws Exception;

    /**
     * 用于响应HTTP请求,产生具体的HTTP响应结果.HTTPServer1服务器在响应HTTP请求时会调用实现了Servlet接口的特定类的service()
     */
    void service(byte[] requestBuffer, OutputStream out) throws Exception;
}

Servlet实现类

package server;

import java.io.OutputStream;

public class HelloServlet implements Servlet {
    @Override
    public void init() throws Exception {
        System.out.println("HelloServlet is inited");
    }

    /**
     * 解析HTTP请求中的请求参数,并
     */
    @Override
    public void service(byte[] requestBuffer, OutputStream out) throws Exception {
        String request = new String(requestBuffer);

        String firstLineOfRequest = request.substring(0, request.indexOf("\r\n"));
        String[] parts = firstLineOfRequest.split(" ");
        String method = parts[0];
        String uri = parts[1];
        String username = null;

        if ("get".equals(method) && uri.contains("username=")) {
            /**
             * 假定uri="servlet/HelloServlet?username=Tom&password=1234
             */
            String parameters = uri.substring(uri.indexOf("?"), uri.length());

            parts = parameters.split("&");
            parts = parts[0].split("=");
            username = parts[1];
        }

        if ("post".equals(method)) {
            int locate = request.indexOf("\r\n\r\n");
            String content = request.substring(locate + 4, request.length());
            if (content.contains("username=")) {
                parts = content.split("&");
                parts = parts[0].split("=");
                username = parts[1];
            }
        }

        out.write("HTTP/1.1 200 OK\r\n".getBytes());
        out.write("Content-Type:text/html\r\n\r\n".getBytes());
        out.write("<html><head><title>HelloWorld</title></head><body>".getBytes());
        out.write(new String("<h1>hello: " + username + "</h1></body></html>").getBytes());
    }
}

1.6.5. 发布基于Web的应用程序,即Web应用

  • 以浏览器作为展示客户端界面的窗口

  • 客户端界面一律表现为网页形式,网页由HTML语言编写,具有交互功能

  • 能完成与桌面应用程序类型的功能

  • 使用浏览器/服务器架构(B/S),浏览器与服务器之间采用HTTP协议通信

  • Web应用通过Web服务器来发布

    • 发展出了MVC设计模式

1.6.6. 发布Web服务

  • Web服务简单理解,被客户端远程调用的各种办法
    • Web服务架构采用SOAP(Simple Object Access Protocol,简单对象访问协议)
      • SOAP规定客户与服务器一律用XML语言进行通信.(XML Extensible Markup Language,可扩展标记语言)

1.6.7. 推出Web2.0,它是全民共建的Web

1.7 处理HTTP请求参数及HTML表单

  • get

    • 例子: http://localhost:8080/servlet/HelloServlet?username=Tom&password=123456
    • "?"后面的字符串就是HTTP请求参数.
    • HTTP请求参数是客户端向服务器端发送的字符串形式的数据,采用"参数名=参数值"的格式,多个参数用"&"隔开
    • get请求参数放在HTTP请求的第一行的URI后面.例: GET /servlet/HelloServlet?username=Tom&password=123456 HTTP/1.1
  • post

    • post请求参数放在HTTP请求的正文部分

    • ** HTML表单

      ** <form method="post" action="servlet/HelloServlet">

      • method属性指定请求方式一般为post
      • action属性指定用户提交表单时,浏览器请求的URI
    • input框 <input type="text" name="username" value="Tom">

      • name属性对应请求参数名,value属性对应请求参数值

1.8 客户端向服务器端上传文件

HTML

<from name="uploadForm" method="POST" enctype="MULTIPART/FORM-DATA" action="serlet/UpLoadServlet">
<input type="file" name="filedata">

UploadServlet

package server;

import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.StringReader;

public class UploadServlet implements Servlet {
    @Override
    public void init() throws Exception {
        System.out.println("UploadServlet is inited");
    }

    @Override
    public void service(byte[] requestBuffer, OutputStream out) throws Exception {
        String request = new String(requestBuffer);

        // 获得HTTP请求的头
        String headerOfRequest = request.substring(request.indexOf("\r\n") + 2, request.indexOf("\r\n\r\n"));

        BufferedReader br = new BufferedReader(new StringReader(headerOfRequest));
        String data = null;

        String boundary = null;
        while ((data = br.readLine()) != null) {
            if (data.contains("Content-Type")) {
                boundary = data.substring(data.indexOf("boundary=") + 9, data.length()) + "\r\n";
                break;
            }
        }

        if (boundary == null) {
            out.write("HTTP/1.1 200 OK\r\n".getBytes());
            out.write("Content-Type:text/html\r\n\r\n".getBytes());
            out.write("Uploading is failed".getBytes());
            return;
        }

        // 第一个boundary出现的位置
        int index1OfBoundary = request.indexOf(boundary);
        int index2OfBoundary = request.indexOf(boundary, index1OfBoundary + boundary.length());
        int index3OfBoundary = request.indexOf(boundary, index2OfBoundary + boundary.length());

        // 文件部分的正文部分开始前的位置
        int beforeOfFilePart = request.indexOf("\r\n\r\n", index2OfBoundary) + 3;
        // 文件部分的正文部分结束后的位置
        int afterOfFilePart = index3OfBoundary - 4;
        // 文件部分的头的第一行结束后的位置
        int afterOfFilePartLine1 = request.indexOf("\r\n", index2OfBoundary + boundary.length());
        // 文件部分的头的第二行
        String header2OfFilePart = request.substring(index2OfBoundary + boundary.length(), afterOfFilePartLine1);
        // 上传文件的名字
        String fileName = header2OfFilePart.substring(header2OfFilePart.lastIndexOf("\\") + 1,
                header2OfFilePart.length() - 1);
        // 文件部分的正文部分之后的字符串的字节长度
        int len1 = request.substring(0, beforeOfFilePart + 1).getBytes().length;
        // 文件部分的正文部分之后的字符串的字节长度
        int len2 = request.substring(afterOfFilePart, request.length()).getBytes().length;
        // 文件部分的正文部分的字节长度
        int fileLen = requestBuffer.length - len1 - len2;

        FileOutputStream f = new FileOutputStream("server\\root\\" + fileName);
        f.write(requestBuffer, len1, fileLen);
        f.close();

        out.write("HTTP/1.1 200 OK\r\n".getBytes());
        out.write("Content-Type:text/html\r\n\r\n".getBytes());
        out.write("<html><head><title>HelloWorld</title></head><body>".getBytes());
        out.write(new String("<h1>Uploading is finished.<br></h1>").getBytes());
        out.write(new String("<h1>FileName: " + fileName + "<br></h1>").getBytes());
        out.write(new String("<h1>FileSize: " + fileLen + "<br></h1></body></html>").getBytes());
    }
}
posted @ 2021-01-21 07:59  hj0612  阅读(86)  评论(0编辑  收藏  举报