深入剖析Tomcat(三) 实现一个简易连接器

Tomcat中的servlet容器叫做Catalina,Catalina有两个主要模块:连接器与容器。在本章,将会建立一个连接器来增强第二章中应用程序的功能,用一种更好的方式来创建request与response对象。

截止文章编写日期,servlet规范已经出到了6.0版本,但是连接器的基本功能没变,都是需要创建javax.servlet.http.HttpServletRequest实例与javax.servlet.http.HttpServletResponse实例,并将它们作为servlet#service方法的参数传入。

本章内容将我们这个Web容器拆分成三个模块:启动模块,连接器模块,servlet容器模块,包的规划如下图

servlet容器模块本章不做扩展,仍然使用前一章的ServletProcessor与StaticResourceProcessor。本章主要聚焦连接器模块,即connector包下的内容。

从本章开始,每章的应用程序中都会有一个启动类来启动整个应用程序,但是目前还没有一种机制来关闭应用程序,这个到指定章节再做实现。目前只能通过杀进程的方式来关闭应用。

在正式介绍本章的程序设计之前,先来看看Tomcat中的一个处理错误消息的类org.apache.catalina.util.StringManager

StringManager类

这个类需要搭配一个文件来运作:LocalStrings.properties。来看这个文件中放的什么格式的内容

啧,全是key,value的形式,它的目的就是针对某一类错误,定义了一个统一的报错文案,如果要改文案的话直接改这个文件中的就可以,避免写的太分散不好改。

另外这种单提出来文件的形式,也方便做国际化的设计,例如Tomcat为了支持西班牙语与日语,创建了以 _es与_ja 为后缀的文件,三个文件内容保持key相同,value值定为指定语言的文案即可。

LocalStrings.properties的生效范围为当前包,也就是说它仅针对它所在包中的错误做定义,所以不可避免的在Tomcat源码中,有很多包下都存在LocalStrings.properties文件。

再回来看StringManager这个类,这个类就是要利用起来这些LocalStrings.properties文件。由于LocalStrings.properties文件是按包划分的,StringManager对象也按包划分,每个包用一个StringManager对象。

StringManager中用一个HashTable来保存各个包下的StringManager对象

private static Hashtable managers = new Hashtable();

/**
 * 获取特定包的StringManager。如果managers中已经存在,它将被重用,否则将创建并返回一个新的StringManager。
 */
public synchronized static StringManager getManager(String packageName) {
    StringManager mgr = (StringManager)managers.get(packageName);
    if (mgr == null) {
        mgr = new StringManager(packageName);
        managers.put(packageName, mgr);
    }
    return mgr;
}

使用StringManager的方法如下

如果在ex03.hml.connector.http包下,获取其StringManager的方法为

StringManager sm = StringManager.getManager("ex03.hml.connector.http");

使用方法为

sm.getString("httpProcessor.parseHeaders.colon")

这样就拿到了指定包下LocalStrings.properties文件中定义的错误信息。

下面正式开始介绍本章的程序设计

本章程序设计

上面讲到了,本章程序将由三个模块组成(启动模块,连接器模块,servlet容器模块),接下来分别看下各自模块的设计

第二章的HttpServer类既做了服务的启动又做了http请求的连接功能,本章将HttpServer拆成两块内容,启动模块与连接器模块。

启动模块

启动模块只有一个类Bootstrap,负责启动整个应用程序。

连接器模块

连接器涉及的类比较多,可以分为以下5个类型

  • 连接器及其支持类(HttpConnector与HttpProcessor),HttpConnector负责接收http请求,HttpProcessor负责将http请求解析为HttpRequest与HttpResponse对象。
  • 表示HTTP请求的类(HttpRequest)及其支持类
  • 表示HTTP响应的类(HttpResponse)及其支持类
  • 外观类(HttpRequestFacade与HttpResponseFacade)
  • 常量类

servlet容器模块

servlet容器模块包含ServletProcessor与StaticResourceProcessor两个类,这两个类与第二章的代码并无太大区别。

本章应用程序的UML图如下

接下来看具体的程序代码

启动类-Bootstrap

此类很简单,就是一个main方法,用来启动一个连接器

package ex03.hml.startup;

import ex03.hml.connector.http.HttpConnector;

/**
 * 启动器,用于启动一个Web应用
 */
public final class Bootstrap {
  public static void main(String[] args) {
    HttpConnector connector = new HttpConnector();
    connector.start();
  }
}

连接器类-HttpConnector

连接器实现了Runnable接口,以一个独立线程的方式来启动。HttpConnector只负责不断地接收Socket连接,具体对Scoket连接的处理交给HttpProcessor来完成。

package ex03.hml.connector.http;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 连接器,用于接收socket连接(一次http请求建立一次连接,http返回后销毁连接)
 * 此连接器是以一个独立线程的方式启动起来的
 */
public class HttpConnector implements Runnable {

    boolean stopped;

    // scheme这个属性在本章暂时没地方用到
    private String scheme = "http";

    public String getScheme() {
        return scheme;
    }

    public void run() {
        // 创建一个ServerSocket用来接收客户端的Socket连接
        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);
        }
        // 建立循环,不停的等待并处理socket连接,这里虽然设了停止标记(stopped),但是暂时没用到,停止服务仍然采用终止进程的方式,
        // 如何优雅的停止服务在后面的章节会有设计
        while (!stopped) {
            Socket socket;
            try {
                // 阻塞等待下一个 Socket 连接
                socket = serverSocket.accept();
            } catch (Exception e) {
                continue;
            }
            // 新建一个HttpProcessor来处理此 Socket 请求
            HttpProcessor processor = new HttpProcessor(this);
            processor.process(socket);
        }
    }

    /**
     * 启动连接器的线程
     */
    public void start() {
        Thread thread = new Thread(this);
        thread.start();
    }
}

接下来其实应该看HttpProcessor具体处理HTTP请求的过程,不过,由于HttpProcessor类依赖了好几个其他类,所以在介绍HttpProcessor之前,先介绍一下它依赖的几个类:HttpRequestLine、HttpHeader、SocketInputStream、HttpRequest、HttpResponse、HttpRequestFacade、HttpResponseFacade。

HttpRequestLine类

一个处理HTTP请求的过程中间类,保存HTTP请求行的信息,便于转化为HttpRequest类中的 method、uri、protocol、queryString字段

HttpRequestLine对象中的这些属性值的填充将被 SocketInputStream 的 readRequestLine 方法实现。

源码大概看一下就行

package ex03.hml.connector.http;


/**
 * HTTP request line enum type.
 *
 * @author Remy Maucherat
 * @version $Revision: 1.6 $ $Date: 2002/03/18 07:15:40 $
 * @deprecated
 */

final class HttpRequestLine {


    // -------------------------------------------------------------- Constants


    public static final int INITIAL_METHOD_SIZE = 8;
    public static final int INITIAL_URI_SIZE = 64;
    public static final int INITIAL_PROTOCOL_SIZE = 8;
    public static final int MAX_METHOD_SIZE = 1024;
    public static final int MAX_URI_SIZE = 32768;
    public static final int MAX_PROTOCOL_SIZE = 1024;


    // ----------------------------------------------------------- Constructors


    public HttpRequestLine() {

        this(new char[INITIAL_METHOD_SIZE], 0, new char[INITIAL_URI_SIZE], 0,
                new char[INITIAL_PROTOCOL_SIZE], 0);

    }


    public HttpRequestLine(char[] method, int methodEnd,
                           char[] uri, int uriEnd,
                           char[] protocol, int protocolEnd) {

        this.method = method;
        this.methodEnd = methodEnd;
        this.uri = uri;
        this.uriEnd = uriEnd;
        this.protocol = protocol;
        this.protocolEnd = protocolEnd;

    }


    // ----------------------------------------------------- Instance Variables
    
    public char[] method;
    public int methodEnd;
    public char[] uri;
    public int uriEnd;
    public char[] protocol;
    public int protocolEnd;


    // ------------------------------------------------------------- Properties


    // --------------------------------------------------------- Public Methods


    /**
     * 释放所有对象引用,并初始化实例变量为重用该对象做准备。
     */
    public void recycle() {

        methodEnd = 0;
        uriEnd = 0;
        protocolEnd = 0;

    }
    
    /**
     * Test if the uri includes the given char array.
     */
    public int indexOf(char[] buf) {
        return indexOf(buf, buf.length);
    }
    
    /**
     * Test if the value of the header includes the given char array.
     */
    public int indexOf(char[] buf, int end) {
        char firstChar = buf[0];
        int pos = 0;
        while (pos < uriEnd) {
            pos = indexOf(firstChar, pos);
            if (pos == -1)
                return -1;
            if ((uriEnd - pos) < end)
                return -1;
            for (int i = 0; i < end; i++) {
                if (uri[i + pos] != buf[i])
                    break;
                if (i == (end - 1))
                    return pos;
            }
            pos++;
        }
        return -1;
    }
    
    /**
     * Test if the value of the header includes the given string.
     */
    public int indexOf(String str) {
        return indexOf(str.toCharArray(), str.length());
    }


    /**
     * Returns the index of a character in the value.
     */
    public int indexOf(char c, int start) {
        for (int i = start; i < uriEnd; i++) {
            if (uri[i] == c)
                return i;
        }
        return -1;
    }

    // --------------------------------------------------------- Object Methods
    public int hashCode() {
        // FIXME
        return 0;
    }

    public boolean equals(Object obj) {
        return false;
    }
    
}

HttpHeader类

一个处理HTTP请求的过程中间类,保存HTTP请求中请求头的信息,注意一个HttpHeader对象只对应一个请求头,通常情况下一个HTTP请求中会包含多个请求头,解析出来后就是一个 HttpHeader的对象集合。

HttpHeader对象中的这些属性值的填充将被 SocketInputStream 的 readHeader 方法实现。

HttpHeader最终会被转化为 name、value(String类型),放入HttpRequest的 protected HashMap headers = new HashMap(); 属性中。

源码大概看一下就行

package ex03.hml.connector.http;

/**
 * HTTP header enum type.
 *
 * @author Remy Maucherat
 * @version $Revision: 1.4 $ $Date: 2002/03/18 07:15:40 $
 * @deprecated
 */

final class HttpHeader {

    // -------------------------------------------------------------- Constants


    public static final int INITIAL_NAME_SIZE = 32;
    public static final int INITIAL_VALUE_SIZE = 64;
    public static final int MAX_NAME_SIZE = 128;
    public static final int MAX_VALUE_SIZE = 4096;


    // ----------------------------------------------------------- Constructors
    
    public HttpHeader() {

        this(new char[INITIAL_NAME_SIZE], 0, new char[INITIAL_VALUE_SIZE], 0);

    }


    public HttpHeader(char[] name, int nameEnd, char[] value, int valueEnd) {

        this.name = name;
        this.nameEnd = nameEnd;
        this.value = value;
        this.valueEnd = valueEnd;

    }
    
    public HttpHeader(String name, String value) {

        this.name = name.toLowerCase().toCharArray();
        this.nameEnd = name.length();
        this.value = value.toCharArray();
        this.valueEnd = value.length();

    }
    
    // ----------------------------------------------------- Instance Variables
    
    public char[] name;
    public int nameEnd;
    public char[] value;
    public int valueEnd;
    protected int hashCode = 0;
    
    // ------------------------------------------------------------- Properties


    // --------------------------------------------------------- Public Methods
    
    /**
     * Release all object references, and initialize instance variables, in
     * preparation for reuse of this object.
     */
    public void recycle() {

        nameEnd = 0;
        valueEnd = 0;
        hashCode = 0;

    }


    /**
     * Test if the name of the header is equal to the given char array.
     * All the characters must already be lower case.
     */
    public boolean equals(char[] buf) {
        return equals(buf, buf.length);
    }


    /**
     * Test if the name of the header is equal to the given char array.
     * All the characters must already be lower case.
     */
    public boolean equals(char[] buf, int end) {
        if (end != nameEnd)
            return false;
        for (int i=0; i<end; i++) {
            if (buf[i] != name[i])
                return false;
        }
        return true;
    }


    /**
     * Test if the name of the header is equal to the given string.
     * The String given must be made of lower case characters.
     */
    public boolean equals(String str) {
        return equals(str.toCharArray(), str.length());
    }


    /**
     * Test if the value of the header is equal to the given char array.
     */
    public boolean valueEquals(char[] buf) {
        return valueEquals(buf, buf.length);
    }


    /**
     * Test if the value of the header is equal to the given char array.
     */
    public boolean valueEquals(char[] buf, int end) {
        if (end != valueEnd)
            return false;
        for (int i=0; i<end; i++) {
            if (buf[i] != value[i])
                return false;
        }
        return true;
    }


    /**
     * Test if the value of the header is equal to the given string.
     */
    public boolean valueEquals(String str) {
        return valueEquals(str.toCharArray(), str.length());
    }


    /**
     * Test if the value of the header includes the given char array.
     */
    public boolean valueIncludes(char[] buf) {
        return valueIncludes(buf, buf.length);
    }


    /**
     * Test if the value of the header includes the given char array.
     */
    public boolean valueIncludes(char[] buf, int end) {
        char firstChar = buf[0];
        int pos = 0;
        while (pos < valueEnd) {
            pos = valueIndexOf(firstChar, pos);
            if (pos == -1)
                return false;
            if ((valueEnd - pos) < end)
                return false;
            for (int i = 0; i < end; i++) {
                if (value[i + pos] != buf[i])
                    break;
                if (i == (end-1))
                    return true;
            }
            pos++;
        }
        return false;
    }


    /**
     * Test if the value of the header includes the given string.
     */
    public boolean valueIncludes(String str) {
        return valueIncludes(str.toCharArray(), str.length());
    }


    /**
     * Returns the index of a character in the value.
     */
    public int valueIndexOf(char c, int start) {
        for (int i=start; i<valueEnd; i++) {
            if (value[i] == c)
                return i;
        }
        return -1;
    }


    /**
     * Test if the name of the header is equal to the given header.
     * All the characters in the name must already be lower case.
     */
    public boolean equals(HttpHeader header) {
        return (equals(header.name, header.nameEnd));
    }


    /**
     * Test if the name and value of the header is equal to the given header.
     * All the characters in the name must already be lower case.
     */
    public boolean headerEquals(HttpHeader header) {
        return (equals(header.name, header.nameEnd))
            && (valueEquals(header.value, header.valueEnd));
    }


    // --------------------------------------------------------- Object Methods


    /**
     * Return hash code. The hash code of the HttpHeader object is the same
     * as returned by new String(name, 0, nameEnd).hashCode().
     */
    public int hashCode() {
        int h = hashCode;
        if (h == 0) {
            int off = 0;
            char val[] = name;
            int len = nameEnd;
            for (int i = 0; i < len; i++)
                h = 31*h + val[off++];
            hashCode = h;
        }
        return h;
    }


    public boolean equals(Object obj) {
        if (obj instanceof String) {
            return equals(((String) obj).toLowerCase());
        } else if (obj instanceof HttpHeader) {
            return equals((HttpHeader) obj);
        }
        return false;
    }


}

 SocketInputStream类

引入此类主要就是为了使用 readRequestLine 与 readHeader 两个方法,其实现逻辑比较晦涩,你且知道这两个方法是干啥的就行

  • public void readRequestLine(HttpRequestLine requestLine):解析InputStream,填充requestLine对象的属性值。
  • public void readHeader(HttpHeader header):解析InputStream,读取到下一个请求头的信息,填充header对象的属性值。

另外有一点需要注意InputStream流的读取过程应该是从头至尾按顺序读的,所以应该先获取请求行,再获取请求头,最后获取body体。

这里说的InputStream就是Socket的InputStream,本章接下来提到的InputStream如果没有特别声明的话,都是Socket的InputStream。

package ex03.hml.connector.http;

import org.apache.catalina.util.StringManager;

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

/**
 * Extends InputStream to be more efficient reading lines during HTTP
 * header processing.
 *
 * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
 * @deprecated
 */
public class SocketInputStream extends InputStream {


    // -------------------------------------------------------------- Constants


    /**
     * CR.
     */
    private static final byte CR = (byte) '\r';


    /**
     * LF.
     */
    private static final byte LF = (byte) '\n';


    /**
     * SP.
     */
    private static final byte SP = (byte) ' ';


    /**
     * HT.
     */
    private static final byte HT = (byte) '\t';


    /**
     * COLON.
     */
    private static final byte COLON = (byte) ':';


    /**
     * Lower case offset.
     */
    private static final int LC_OFFSET = 'A' - 'a';


    /**
     * Internal buffer.
     */
    protected byte buf[];


    /**
     * Last valid byte.
     */
    protected int count;


    /**
     * Position in the buffer.
     */
    protected int pos;


    /**
     * Underlying input stream.
     */
    protected InputStream is;


    // ----------------------------------------------------------- Constructors


    /**
     * Construct a servlet input stream associated with the specified socket
     * input.
     *
     * @param is         socket input stream
     * @param bufferSize size of the internal buffer
     */
    public SocketInputStream(InputStream is, int bufferSize) {

        this.is = is;
        buf = new byte[bufferSize];

    }


    // -------------------------------------------------------------- Variables


    /**
     * The string manager for this package.
     */
    protected static StringManager sm = StringManager.getManager(Constants.Package);


    // ----------------------------------------------------- Instance Variables


    // --------------------------------------------------------- Public Methods


    /**
     * 读取请求行,并将其复制到给定的缓冲区。其实就是解析InputStream,填充HttpRequestLine对象的属性值
     * 这函数是在HTTP请求头解析期间使用的。不要试图使用它来读取请求体。
     *
     * @param requestLine HttpRequestLine 对象
     * @throws IOException 如果在底层套接字期间发生异常读取操作,或者如果给定的缓冲区不够大来容纳整个请求行。
     */
    public void readRequestLine(HttpRequestLine requestLine) throws IOException {

        // Recycling check
        if (requestLine.methodEnd != 0) requestLine.recycle();

        // Checking for a blank line
        int chr;

        // Skipping CR or LF
        do {
            try {
                chr = read();
            } catch (IOException e) {
                chr = -1;
            }
        } while ((chr == CR) || (chr == LF));

        if (chr == -1) throw new EOFException(sm.getString("requestStream.readline.error"));
        pos--;

        // Reading the method name

        int maxRead = requestLine.method.length;
        int readStart = pos;
        int readCount = 0;

        boolean space = false;

        while (!space) {
            // if the buffer is full, extend it
            if (readCount >= maxRead) {
                if ((2 * maxRead) <= HttpRequestLine.MAX_METHOD_SIZE) {
                    char[] newBuffer = new char[2 * maxRead];
                    System.arraycopy(requestLine.method, 0, newBuffer, 0, maxRead);
                    requestLine.method = newBuffer;
                    maxRead = requestLine.method.length;
                } else {
                    throw new IOException(sm.getString("requestStream.readline.toolong"));
                }
            }
            // We're at the end of the internal buffer
            if (pos >= count) {
                int val = read();
                if (val == -1) {
                    throw new IOException(sm.getString("requestStream.readline.error"));
                }
                pos = 0;
                readStart = 0;
            }
            if (buf[pos] == SP) {
                space = true;
            }
            requestLine.method[readCount] = (char) buf[pos];
            readCount++;
            pos++;
        }

        requestLine.methodEnd = readCount - 1;

        // Reading URI

        maxRead = requestLine.uri.length;
        readStart = pos;
        readCount = 0;

        space = false;

        boolean eol = false;

        while (!space) {
            // if the buffer is full, extend it
            if (readCount >= maxRead) {
                if ((2 * maxRead) <= HttpRequestLine.MAX_URI_SIZE) {
                    char[] newBuffer = new char[2 * maxRead];
                    System.arraycopy(requestLine.uri, 0, newBuffer, 0, maxRead);
                    requestLine.uri = newBuffer;
                    maxRead = requestLine.uri.length;
                } else {
                    throw new IOException(sm.getString("requestStream.readline.toolong"));
                }
            }
            // We're at the end of the internal buffer
            if (pos >= count) {
                int val = read();
                if (val == -1) throw new IOException(sm.getString("requestStream.readline.error"));
                pos = 0;
                readStart = 0;
            }
            if (buf[pos] == SP) {
                space = true;
            } else if ((buf[pos] == CR) || (buf[pos] == LF)) {
                // HTTP/0.9 style request
                eol = true;
                space = true;
            }
            requestLine.uri[readCount] = (char) buf[pos];
            readCount++;
            pos++;
        }

        requestLine.uriEnd = readCount - 1;

        // Reading protocol

        maxRead = requestLine.protocol.length;
        readStart = pos;
        readCount = 0;

        while (!eol) {
            // if the buffer is full, extend it
            if (readCount >= maxRead) {
                if ((2 * maxRead) <= HttpRequestLine.MAX_PROTOCOL_SIZE) {
                    char[] newBuffer = new char[2 * maxRead];
                    System.arraycopy(requestLine.protocol, 0, newBuffer, 0, maxRead);
                    requestLine.protocol = newBuffer;
                    maxRead = requestLine.protocol.length;
                } else {
                    throw new IOException(sm.getString("requestStream.readline.toolong"));
                }
            }
            // We're at the end of the internal buffer
            if (pos >= count) {
                // Copying part (or all) of the internal buffer to the line
                // buffer
                int val = read();
                if (val == -1) throw new IOException(sm.getString("requestStream.readline.error"));
                pos = 0;
                readStart = 0;
            }
            if (buf[pos] == CR) {
                // Skip CR.
            } else if (buf[pos] == LF) {
                eol = true;
            } else {
                requestLine.protocol[readCount] = (char) buf[pos];
                readCount++;
            }
            pos++;
        }

        requestLine.protocolEnd = readCount;
    }


    /**
     * 读取header,并将其复制到给定的缓冲区。其实就是从InputStream中解析出下一个请求头的信息,填充进HttpHeader对象
     * 该函数将在HTTP请求头解析期间使用。不要试图使用它来读取请求体。
     *
     * @param header HttpHeader 对象
     * @throws IOException 如果在底层套接字读取操作期间发生异常,或者给定的缓冲区不够大,无法容纳整行。
     */
    public void readHeader(HttpHeader header) throws IOException {

        // Recycling check
        if (header.nameEnd != 0) header.recycle();

        // Checking for a blank line
        int chr = read();
        if ((chr == CR) || (chr == LF)) { // Skipping CR
            if (chr == CR) read(); // Skipping LF
            header.nameEnd = 0;
            header.valueEnd = 0;
            return;
        } else {
            pos--;
        }

        // Reading the header name

        int maxRead = header.name.length;
        int readStart = pos;
        int readCount = 0;

        boolean colon = false;

        while (!colon) {
            // if the buffer is full, extend it
            if (readCount >= maxRead) {
                if ((2 * maxRead) <= HttpHeader.MAX_NAME_SIZE) {
                    char[] newBuffer = new char[2 * maxRead];
                    System.arraycopy(header.name, 0, newBuffer, 0, maxRead);
                    header.name = newBuffer;
                    maxRead = header.name.length;
                } else {
                    throw new IOException(sm.getString("requestStream.readline.toolong"));
                }
            }
            // We're at the end of the internal buffer
            if (pos >= count) {
                int val = read();
                if (val == -1) {
                    throw new IOException(sm.getString("requestStream.readline.error"));
                }
                pos = 0;
                readStart = 0;
            }
            if (buf[pos] == COLON) {
                colon = true;
            }
            char val = (char) buf[pos];
            if ((val >= 'A') && (val <= 'Z')) {
                val = (char) (val - LC_OFFSET);
            }
            header.name[readCount] = val;
            readCount++;
            pos++;
        }

        header.nameEnd = readCount - 1;

        // Reading the header value (which can be spanned over multiple lines)

        maxRead = header.value.length;
        readStart = pos;
        readCount = 0;

        int crPos = -2;

        boolean eol = false;
        boolean validLine = true;

        while (validLine) {

            boolean space = true;

            // Skipping spaces
            // Note : Only leading white spaces are removed. Trailing white
            // spaces are not.
            while (space) {
                // We're at the end of the internal buffer
                if (pos >= count) {
                    // Copying part (or all) of the internal buffer to the line
                    // buffer
                    int val = read();
                    if (val == -1) throw new IOException(sm.getString("requestStream.readline.error"));
                    pos = 0;
                    readStart = 0;
                }
                if ((buf[pos] == SP) || (buf[pos] == HT)) {
                    pos++;
                } else {
                    space = false;
                }
            }

            while (!eol) {
                // if the buffer is full, extend it
                if (readCount >= maxRead) {
                    if ((2 * maxRead) <= HttpHeader.MAX_VALUE_SIZE) {
                        char[] newBuffer = new char[2 * maxRead];
                        System.arraycopy(header.value, 0, newBuffer, 0, maxRead);
                        header.value = newBuffer;
                        maxRead = header.value.length;
                    } else {
                        throw new IOException(sm.getString("requestStream.readline.toolong"));
                    }
                }
                // We're at the end of the internal buffer
                if (pos >= count) {
                    // Copying part (or all) of the internal buffer to the line
                    // buffer
                    int val = read();
                    if (val == -1) throw new IOException(sm.getString("requestStream.readline.error"));
                    pos = 0;
                    readStart = 0;
                }
                if (buf[pos] == CR) {
                } else if (buf[pos] == LF) {
                    eol = true;
                } else {
                    // FIXME : Check if binary conversion is working fine
                    int ch = buf[pos] & 0xff;
                    header.value[readCount] = (char) ch;
                    readCount++;
                }
                pos++;
            }

            int nextChr = read();

            if ((nextChr != SP) && (nextChr != HT)) {
                pos--;
                validLine = false;
            } else {
                eol = false;
                // if the buffer is full, extend it
                if (readCount >= maxRead) {
                    if ((2 * maxRead) <= HttpHeader.MAX_VALUE_SIZE) {
                        char[] newBuffer = new char[2 * maxRead];
                        System.arraycopy(header.value, 0, newBuffer, 0, maxRead);
                        header.value = newBuffer;
                        maxRead = header.value.length;
                    } else {
                        throw new IOException(sm.getString("requestStream.readline.toolong"));
                    }
                }
                header.value[readCount] = ' ';
                readCount++;
            }

        }

        header.valueEnd = readCount;

    }


    /**
     * Read byte.
     */
    public int read() throws IOException {
        if (pos >= count) {
            fill();
            if (pos >= count) return -1;
        }
        return buf[pos++] & 0xff;
    }


    /**
     *
     */
    /*
    public int read(byte b[], int off, int len)
        throws IOException {

    }
    */


    /**
     *
     */
    /*
    public long skip(long n)
        throws IOException {

    }
    */


    /**
     * Returns the number of bytes that can be read from this input
     * stream without blocking.
     */
    public int available() throws IOException {
        return (count - pos) + is.available();
    }


    /**
     * Close the input stream.
     */
    public void close() throws IOException {
        if (is == null) return;
        is.close();
        is = null;
        buf = null;
    }


    // ------------------------------------------------------ Protected Methods


    /**
     * Fill the internal buffer using data from the undelying input stream.
     */
    protected void fill() throws IOException {
        pos = 0;
        count = 0;
        int nRead = is.read(buf, 0, buf.length);
        if (nRead > 0) {
            count = nRead;
        }
    }
    
}

HttpRequest类

HttpRequest实现了HttpServletRequest接口,不过大多数接口方法都未具体实现。但是经过HttpProcessor处理后,servlet程序员已经可以从中获取HTTP请求的请求头,Cookie和请求参数的信息了。这三类数据分别存在以下三个变量中

protected HashMap headers = new HashMap();
protected ArrayList cookies = new ArrayList();
protected ParameterMap parameters = null;

这样servlet中就可以调用HttpRequest的 getHeader()、getCookies()、getParameter()等一系列相关的方法了。

该类中也提供了addCookie()、addHeader()方法来给HttpRequest填充对应属性。填充parameters属性的方法单独说一下

HttpRequest中持有一个InputStream对象的引用,并对外提供了parseParameters()方法,以便在合适的时机去解析请求参数。

为什么说“合适的时机”呢? 因为并不是所有servlet都需要获取请求参数的,而解析请求参数又是一个耗时耗费资源的过程,所以在需要时调用会更合理。

什么时候“需要”呢?当servlet调用HttpRequest中获取请求参数的方法时就是需要的时候,如getParameter()、getParameterMap()、getParameterNames()、getParameterValues()等方法。当然parseParameters()也会只保证执行一次(执行完后给parsed标记设为true),不会重复执行做无用功。

另外parameters这个Map的类型是ParameterMap,它继承了HashMap,并持有一个boolean locked字段,字段为true时才可对parameters进行修改操作,字段为false时不允许操作,防止其他程序篡改HTTP消息。

HttpRequest类代码如下,很多留空的方法,大概看一下上面提到的属性和方法就行

package ex03.hml.connector.http;

/** this class copies methods from org.apache.catalina.connector.HttpRequestBase
 *  and org.apache.catalina.connector.http.HttpRequestImpl.
 *  The HttpRequestImpl class employs a pool of HttpHeader objects for performance
 *  These two classes will be explained in Chapter 4.
 */

import ex03.hml.connector.RequestStream;
import org.apache.catalina.util.Enumerator;
import org.apache.catalina.util.ParameterMap;
import org.apache.catalina.util.RequestUtil;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletInputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.security.Principal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

public class HttpRequest implements HttpServletRequest {

  private String contentType;
  private int contentLength;
  private InetAddress inetAddress;
  private InputStream input;
  private String method;
  private String protocol;
  private String queryString;
  private String requestURI;
  private String serverName;
  private int serverPort;
  private Socket socket;
  private boolean requestedSessionCookie;  // session在cookie中声明
  private String requestedSessionId;
  private boolean requestedSessionURL;    // session在URL中声明

  /**
   * The request attributes for this request.
   */
  protected HashMap attributes = new HashMap();
  /**
   * The authorization credentials sent with this Request.
   */
  protected String authorization = null;
  /**
   * The context path for this request.
   */
  protected String contextPath = "";
  /**
   * The set of cookies associated with this Request.
   */
  protected ArrayList cookies = new ArrayList();
  /**
   * An empty collection to use for returning empty Enumerations.  Do not
   * add any elements to this collection!
   */
  protected static ArrayList empty = new ArrayList();
  /**
   * The set of SimpleDateFormat formats to use in getDateHeader().
   */
  protected SimpleDateFormat formats[] = {
    new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),
    new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
    new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US)
  };

  /**
   * The HTTP headers associated with this Request, keyed by name.  The
   * values are ArrayLists of the corresponding header values.
   */
  protected HashMap headers = new HashMap();
  /**
   * The parsed parameters for this request.  This is populated only if
   * parameter information is requested via one of the
   * <code>getParameter()</code> family of method calls.  The key is the
   * parameter name, while the value is a String array of values for this
   * parameter.
   * <p>
   * <strong>IMPLEMENTATION NOTE</strong> - Once the parameters for a
   * particular request are parsed and stored here, they are not modified.
   * Therefore, application level access to the parameters need not be
   * synchronized.
   */
  protected ParameterMap parameters = null;

  /**
   * Have the parameters for this request been parsed yet?
   */
  protected boolean parsed = false;
  protected String pathInfo = null;

  /**
   * The reader that has been returned by <code>getReader</code>, if any.
   */
  protected BufferedReader reader = null;

  /**
   * The ServletInputStream that has been returned by
   * <code>getInputStream()</code>, if any.
   */
  protected ServletInputStream stream = null;

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

  public void addHeader(String name, String value) {
    name = name.toLowerCase();
    synchronized (headers) {
      ArrayList values = (ArrayList) headers.get(name);
      if (values == null) {
        values = new ArrayList();
        headers.put(name, values);
      }
      values.add(value);
    }
  }

  /**
   * Parse the parameters of this request, if it has not already occurred.
   * If parameters are present in both the query string and the request
   * content, they are merged.
   */
  protected void parseParameters() {
    if (parsed)
      return;
    ParameterMap results = parameters;
    if (results == null)
      results = new ParameterMap();
    results.setLocked(false);
    String encoding = getCharacterEncoding();
    if (encoding == null)
      encoding = "ISO-8859-1";

    // Parse any parameters specified in the query string
    String queryString = getQueryString();
    try {
      RequestUtil.parseParameters(results, queryString, encoding);
    }
    catch (UnsupportedEncodingException e) {
      ;
    }

    // Parse any parameters specified in the input stream
    String contentType = getContentType();
    if (contentType == null)
      contentType = "";
    int semicolon = contentType.indexOf(';');
    if (semicolon >= 0) {
      contentType = contentType.substring(0, semicolon).trim();
    }
    else {
      contentType = contentType.trim();
    }
    if ("POST".equals(getMethod()) && (getContentLength() > 0)
      && "application/x-www-form-urlencoded".equals(contentType)) {
      try {
        int max = getContentLength();
        int len = 0;
        byte buf[] = new byte[getContentLength()];
        ServletInputStream is = getInputStream();
        while (len < max) {
          int next = is.read(buf, len, max - len);
          if (next < 0 ) {
            break;
          }
          len += next;
        }
        is.close();
        if (len < max) {
          throw new RuntimeException("Content length mismatch");
        }
        RequestUtil.parseParameters(results, buf, encoding);
      }
      catch (UnsupportedEncodingException ue) {
        ;
      }
      catch (IOException e) {
        throw new RuntimeException("Content read fail");
      }
    }

    // Store the final results
    results.setLocked(true);
    parsed = true;
    parameters = results;
  }

  public void addCookie(Cookie cookie) {
    synchronized (cookies) {
      cookies.add(cookie);
    }
  }

  /**
   * Create and return a ServletInputStream to read the content
   * associated with this Request.  The default implementation creates an
   * instance of RequestStream associated with this request, but this can
   * be overridden if necessary.
   *
   * @exception IOException if an input/output error occurs
   */
  public ServletInputStream createInputStream() throws IOException {
    return (new RequestStream(this));
  }

  public InputStream getStream() {
    return input;
  }
  public void setContentLength(int length) {
    this.contentLength = length;
  }

  public void setContentType(String type) {
    this.contentType = type;
  }

  public void setInet(InetAddress inetAddress) {
    this.inetAddress = inetAddress;
  }

  public void setContextPath(String path) {
    if (path == null)
      this.contextPath = "";
    else
      this.contextPath = path;
  }

  public void setMethod(String method) {
    this.method = method;
  }

  public void setPathInfo(String path) {
    this.pathInfo = path;
  }

  public void setProtocol(String protocol) {
    this.protocol = protocol;
  }

  public void setQueryString(String queryString) {
    this.queryString = queryString;
  }

  public void setRequestURI(String requestURI) {
    this.requestURI = requestURI;
  }
  /**
   * Set the name of the server (virtual host) to process this request.
   *
   * @param name The server name
   */
  public void setServerName(String name) {
    this.serverName = name;
  }
  /**
   * Set the port number of the server to process this request.
   *
   * @param port The server port
   */
  public void setServerPort(int port) {
    this.serverPort = port;
  }

  public void setSocket(Socket socket) {
    this.socket = socket;
  }

  /**
   * Set a flag indicating whether or not the requested session ID for this
   * request came in through a cookie.  This is normally called by the
   * HTTP Connector, when it parses the request headers.
   *
   * @param flag The new flag
   */
  public void setRequestedSessionCookie(boolean flag) {
    this.requestedSessionCookie = flag;
  }

  public void setRequestedSessionId(String requestedSessionId) {
    this.requestedSessionId = requestedSessionId;
  }

  public void setRequestedSessionURL(boolean flag) {
    requestedSessionURL = flag;
  }

  /* implementation of the HttpServletRequest*/
  public Object getAttribute(String name) {
    synchronized (attributes) {
      return (attributes.get(name));
    }
  }

  public Enumeration getAttributeNames() {
    synchronized (attributes) {
      return (new Enumerator(attributes.keySet()));
    }
  }

  public String getAuthType() {
    return null;
  }

  public String getCharacterEncoding() {
    return null;
  }

  public int getContentLength() {
    return contentLength ;
  }

  public String getContentType() {
    return contentType;
  }

  public String getContextPath() {
    return contextPath;
  }

  public Cookie[] getCookies() {
    synchronized (cookies) {
      if (cookies.size() < 1)
        return (null);
      Cookie results[] = new Cookie[cookies.size()];
      return ((Cookie[]) cookies.toArray(results));
    }
  }

  public long getDateHeader(String name) {
    String value = getHeader(name);
    if (value == null)
      return (-1L);

    // Work around a bug in SimpleDateFormat in pre-JDK1.2b4
    // (Bug Parade bug #4106807)
    value += " ";

    // Attempt to convert the date header in a variety of formats
    for (int i = 0; i < formats.length; i++) {
      try {
        Date date = formats[i].parse(value);
        return (date.getTime());
      }
      catch (ParseException e) {
        ;
      }
    }
    throw new IllegalArgumentException(value);
  }

  public String getHeader(String name) {
    name = name.toLowerCase();
    synchronized (headers) {
      ArrayList values = (ArrayList) headers.get(name);
      if (values != null)
        return ((String) values.get(0));
      else
        return null;
    }
  }

  public Enumeration getHeaderNames() {
    synchronized (headers) {
      return (new Enumerator(headers.keySet()));
    }
  }

  public Enumeration getHeaders(String name) {
    name = name.toLowerCase();
    synchronized (headers) {
      ArrayList values = (ArrayList) headers.get(name);
      if (values != null)
        return (new Enumerator(values));
      else
        return (new Enumerator(empty));
    }
  }

  public ServletInputStream getInputStream() throws IOException {
    if (reader != null)
      throw new IllegalStateException("getInputStream has been called");

    if (stream == null)
      stream = createInputStream();
    return (stream);
  }

  public int getIntHeader(String name) {
    String value = getHeader(name);
    if (value == null)
      return (-1);
    else
      return (Integer.parseInt(value));
  }

  public Locale getLocale() {
    return null;
  }

  public Enumeration getLocales() {
    return null;
  }

  public String getMethod() {
    return method;
  }

  public String getParameter(String name) {
    parseParameters();
    String values[] = (String[]) parameters.get(name);
    if (values != null)
      return (values[0]);
    else
      return (null);
  }

  public Map getParameterMap() {
    parseParameters();
    return (this.parameters);
  }

  public Enumeration getParameterNames() {
    parseParameters();
    return (new Enumerator(parameters.keySet()));
  }

  public String[] getParameterValues(String name) {
    parseParameters();
    String values[] = (String[]) parameters.get(name);
    if (values != null)
      return (values);
    else
      return null;
  }

  public String getPathInfo() {
    return pathInfo;
  }

  public String getPathTranslated() {
    return null;
  }

  public String getProtocol() {
    return protocol;
  }

  public String getQueryString() {
    return queryString;
  }

  public BufferedReader getReader() throws IOException {
    if (stream != null)
      throw new IllegalStateException("getInputStream has been called.");
    if (reader == null) {
      String encoding = getCharacterEncoding();
      if (encoding == null)
        encoding = "ISO-8859-1";
      InputStreamReader isr =
        new InputStreamReader(createInputStream(), encoding);
        reader = new BufferedReader(isr);
    }
    return (reader);
  }

  public String getRealPath(String path) {
    return null;
  }

  public String getRemoteAddr() {
    return null;
  }

  public String getRemoteHost() {
    return null;
  }

  public String getRemoteUser() {
    return null;
  }

  public RequestDispatcher getRequestDispatcher(String path) {
    return null;
  }

  public String getScheme() {
   return null;
  }

  public String getServerName() {
    return null;
  }

  public int getServerPort() {
    return 0;
  }

  public String getRequestedSessionId() {
    return null;
  }

  public String getRequestURI() {
    return requestURI;
  }

  public StringBuffer getRequestURL() {
    return null;
  }

  public HttpSession getSession() {
    return null;
  }

  public HttpSession getSession(boolean create) {
    return null;
  }

  public String getServletPath() {
    return null;
  }

  public Principal getUserPrincipal() {
    return null;
  }

  public boolean isRequestedSessionIdFromCookie() {
    return false;
  }

  public boolean isRequestedSessionIdFromUrl() {
    return isRequestedSessionIdFromURL();
  }

  public boolean isRequestedSessionIdFromURL() {
    return false;
  }

  public boolean isRequestedSessionIdValid() {
    return false;
  }

  public boolean isSecure() {
    return false;
  }

  public boolean isUserInRole(String role) {
    return false;
  }

  public void removeAttribute(String attribute) {
  }

  public void setAttribute(String key, Object value) {
  }

  /**
   * Set the authorization credentials sent with this request.
   *
   * @param authorization The new authorization credentials
   */
  public void setAuthorization(String authorization) {
    this.authorization = authorization;
  }

  public void setCharacterEncoding(String encoding) throws UnsupportedEncodingException {
  }
}

HttpResponse类

HttpResponse类实现了HttpServletResponse接口,对部分接口做了具体实现。相比于第二章的Response类,HttpResponse类拥有更多的属性,例如针对HTTP相应信息的属性:contentType、contentLength、cookies、headers 等等。

HttpResponse提供给servlet往输出流中写数据的方法仍然是提供PrintWriter。但是本章的PrintWriter会使用一个它的子类:ResponseWriter。下面是getWriter的代码

一个往OutputStream中写数据的方法被封装了好几层。OutputStreamWriter可以指定输出内容的字符集;ReponseStream继承自ServletOutputStream,所以它也是当做一个数据流来编码,它持有一个HttpResponse对象,它的write方法是调用的HttpResponse的write方法,使用HttpResponse持有的OutputStream对象将数据写入Socket的输出流中。

ResponseWriter在每个写数据的方法都额外做了一件事,就是调用了OutputStreamWriter的flush方法,其实最终调用的是HttpResponse中持有的OutputStream对象的flush方法。解决了第二章中使用原生PrintWriter的 print 方法时不会刷新输出流的弊端。

读取静态资源的方法仍然保留 sendStaticResource()。

package ex03.hml.connector.http;

import ex03.hml.connector.ResponseStream;
import ex03.hml.connector.ResponseWriter;
import org.apache.catalina.util.CookieTools;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;

public class HttpResponse implements HttpServletResponse {

    // the default buffer size
    private static final int BUFFER_SIZE = 1024;
    HttpRequest request;
    OutputStream output;
    PrintWriter writer;
    protected byte[] buffer = new byte[BUFFER_SIZE];
    protected int bufferCount = 0;
    /**
     * Has this response been committed yet?
     */
    protected boolean committed = false;
    /**
     * The actual number of bytes written to this Response.
     */
    protected int contentCount = 0;
    /**
     * The content length associated with this Response.
     */
    protected int contentLength = -1;
    /**
     * The content type associated with this Response.
     */
    protected String contentType = null;
    /**
     * The character encoding associated with this Response.
     */
    protected String encoding = null;

    /**
     * The set of Cookies associated with this Response.
     */
    protected ArrayList cookies = new ArrayList();
    /**
     * The HTTP headers explicitly added via addHeader(), but not including
     * those to be added with setContentLength(), setContentType(), and so on.
     * This collection is keyed by the header name, and the elements are
     * ArrayLists containing the associated values that have been set.
     */
    protected HashMap headers = new HashMap();
    /**
     * The date format we will use for creating date headers.
     */
    protected final SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
    /**
     * The error message set by <code>sendError()</code>.
     */
    protected String message = getStatusMessage(HttpServletResponse.SC_OK);
    /**
     * The HTTP status code associated with this Response.
     */
    protected int status = HttpServletResponse.SC_OK;


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

    /**
     * call this method to send headers and response to the output
     */
    public void finishResponse() {
        // sendHeaders();
        // Flush and close the appropriate output mechanism
        if (writer != null) {
            writer.flush();
            writer.close();
        }
    }

    public int getContentLength() {
        return contentLength;
    }

    public String getContentType() {
        return contentType;
    }


    protected String getProtocol() {
        return request.getProtocol();
    }

    /**
     * Returns a default status message for the specified HTTP status code.
     *
     * @param status The status code for which a message is desired
     */
    protected String getStatusMessage(int status) {
        switch (status) {
            case SC_OK:
                return ("OK");
            case SC_ACCEPTED:
                return ("Accepted");
            case SC_BAD_GATEWAY:
                return ("Bad Gateway");
            case SC_BAD_REQUEST:
                return ("Bad Request");
            case SC_CONFLICT:
                return ("Conflict");
            case SC_CONTINUE:
                return ("Continue");
            case SC_CREATED:
                return ("Created");
            case SC_EXPECTATION_FAILED:
                return ("Expectation Failed");
            case SC_FORBIDDEN:
                return ("Forbidden");
            case SC_GATEWAY_TIMEOUT:
                return ("Gateway Timeout");
            case SC_GONE:
                return ("Gone");
            case SC_HTTP_VERSION_NOT_SUPPORTED:
                return ("HTTP Version Not Supported");
            case SC_INTERNAL_SERVER_ERROR:
                return ("Internal Server Error");
            case SC_LENGTH_REQUIRED:
                return ("Length Required");
            case SC_METHOD_NOT_ALLOWED:
                return ("Method Not Allowed");
            case SC_MOVED_PERMANENTLY:
                return ("Moved Permanently");
            case SC_MOVED_TEMPORARILY:
                return ("Moved Temporarily");
            case SC_MULTIPLE_CHOICES:
                return ("Multiple Choices");
            case SC_NO_CONTENT:
                return ("No Content");
            case SC_NON_AUTHORITATIVE_INFORMATION:
                return ("Non-Authoritative Information");
            case SC_NOT_ACCEPTABLE:
                return ("Not Acceptable");
            case SC_NOT_FOUND:
                return ("Not Found");
            case SC_NOT_IMPLEMENTED:
                return ("Not Implemented");
            case SC_NOT_MODIFIED:
                return ("Not Modified");
            case SC_PARTIAL_CONTENT:
                return ("Partial Content");
            case SC_PAYMENT_REQUIRED:
                return ("Payment Required");
            case SC_PRECONDITION_FAILED:
                return ("Precondition Failed");
            case SC_PROXY_AUTHENTICATION_REQUIRED:
                return ("Proxy Authentication Required");
            case SC_REQUEST_ENTITY_TOO_LARGE:
                return ("Request Entity Too Large");
            case SC_REQUEST_TIMEOUT:
                return ("Request Timeout");
            case SC_REQUEST_URI_TOO_LONG:
                return ("Request URI Too Long");
            case SC_REQUESTED_RANGE_NOT_SATISFIABLE:
                return ("Requested Range Not Satisfiable");
            case SC_RESET_CONTENT:
                return ("Reset Content");
            case SC_SEE_OTHER:
                return ("See Other");
            case SC_SERVICE_UNAVAILABLE:
                return ("Service Unavailable");
            case SC_SWITCHING_PROTOCOLS:
                return ("Switching Protocols");
            case SC_UNAUTHORIZED:
                return ("Unauthorized");
            case SC_UNSUPPORTED_MEDIA_TYPE:
                return ("Unsupported Media Type");
            case SC_USE_PROXY:
                return ("Use Proxy");
            case 207:       // WebDAV
                return ("Multi-Status");
            case 422:       // WebDAV
                return ("Unprocessable Entity");
            case 423:       // WebDAV
                return ("Locked");
            case 507:       // WebDAV
                return ("Insufficient Storage");
            default:
                return ("HTTP Response Status " + status);
        }
    }

    public OutputStream getStream() {
        return this.output;
    }

    /**
     * Send the HTTP response headers, if this has not already occurred.
     */
    protected void sendHeaders() throws IOException {
        if (isCommitted()) return;
        // Prepare a suitable output writer
        OutputStreamWriter osr = null;
        try {
            osr = new OutputStreamWriter(getStream(), getCharacterEncoding());
        } catch (UnsupportedEncodingException e) {
            osr = new OutputStreamWriter(getStream());
        }
        final PrintWriter outputWriter = new PrintWriter(osr);
        // Send the "Status:" header
        outputWriter.print(this.getProtocol());
        outputWriter.print(" ");
        outputWriter.print(status);
        if (message != null) {
            outputWriter.print(" ");
            outputWriter.print(message);
        }
        outputWriter.print("\r\n");
        // Send the content-length and content-type headers (if any)
        if (getContentType() != null) {
            outputWriter.print("Content-Type: " + getContentType() + "\r\n");
        }
        if (getContentLength() >= 0) {
            outputWriter.print("Content-Length: " + getContentLength() + "\r\n");
        }
        // Send all specified headers (if any)
        synchronized (headers) {
            Iterator names = headers.keySet().iterator();
            while (names.hasNext()) {
                String name = (String) names.next();
                ArrayList values = (ArrayList) headers.get(name);
                Iterator items = values.iterator();
                while (items.hasNext()) {
                    String value = (String) items.next();
                    outputWriter.print(name);
                    outputWriter.print(": ");
                    outputWriter.print(value);
                    outputWriter.print("\r\n");
                }
            }
        }
        // Add the session ID cookie if necessary
/*    HttpServletRequest hreq = (HttpServletRequest) request.getRequest();
    HttpSession session = hreq.getSession(false);
    if ((session != null) && session.isNew() && (getContext() != null)
      && getContext().getCookies()) {
      Cookie cookie = new Cookie("JSESSIONID", session.getId());
      cookie.setMaxAge(-1);
      String contextPath = null;
      if (context != null)
        contextPath = context.getPath();
      if ((contextPath != null) && (contextPath.length() > 0))
        cookie.setPath(contextPath);
      else

      cookie.setPath("/");
      if (hreq.isSecure())
        cookie.setSecure(true);
      addCookie(cookie);
    }
*/
        // Send all specified cookies (if any)
        synchronized (cookies) {
            Iterator items = cookies.iterator();
            while (items.hasNext()) {
                Cookie cookie = (Cookie) items.next();
                outputWriter.print(CookieTools.getCookieHeaderName(cookie));
                outputWriter.print(": ");
                outputWriter.print(CookieTools.getCookieHeaderValue(cookie));
                outputWriter.print("\r\n");
            }
        }

        // Send a terminating blank line to mark the end of the headers
        outputWriter.print("\r\n");
        outputWriter.flush();

        committed = true;
    }

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

    /* This method is used to serve a static page */
    public void sendStaticResource() {
        try {
            if (request.getRequestURI().equals("/shutdown")) {
                String msg = "HTTP/1.1 200 OK\r\n" +
                        "Content-Type: text/html\r\n" +
                        "Content-Length: 32\r\n" +
                        "\r\n" +
                        "<h1>server already shutdown</h1>";
                output.write(msg.getBytes());
                return;
            }


            File file = new File(Constants.WEB_ROOT, request.getRequestURI());
            if (file.exists()) {
                FileInputStream fileInputStream = new FileInputStream(file);
                byte[] bytes = new byte[fileInputStream.available()];
                fileInputStream.read(bytes);
                String successMsg = "HTTP/1.1 200 OK\r\n" +
                        "Content-Type: text/html\r\n" +
                        "Content-Length: " + bytes.length + "\r\n" +
                        "\r\n";
                output.write(successMsg.getBytes());
                output.write(bytes);
                fileInputStream.close();
            } 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 (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (output != null) {
                try {
                    output.flush();
                    output.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    public void write(int b) throws IOException {
        if (bufferCount >= buffer.length) flushBuffer();
        buffer[bufferCount++] = (byte) b;
        contentCount++;
    }

    public void write(byte b[]) throws IOException {
        write(b, 0, b.length);
    }

    public void write(byte b[], int off, int len) throws IOException {
        // If the whole thing fits in the buffer, just put it there
        if (len == 0) return;
        if (len <= (buffer.length - bufferCount)) {
            System.arraycopy(b, off, buffer, bufferCount, len);
            bufferCount += len;
            contentCount += len;
            return;
        }

        // Flush the buffer and start writing full-buffer-size chunks
        flushBuffer();
        int iterations = len / buffer.length;
        int leftoverStart = iterations * buffer.length;
        int leftoverLen = len - leftoverStart;
        for (int i = 0; i < iterations; i++)
            write(b, off + (i * buffer.length), buffer.length);

        // Write the remainder (guaranteed to fit in the buffer)
        if (leftoverLen > 0) write(b, off + leftoverStart, leftoverLen);
    }

    /**
     * implementation of HttpServletResponse
     */

    public void addCookie(Cookie cookie) {
        if (isCommitted()) return;
        //  if (included)
        //        return;     // Ignore any call from an included servlet
        synchronized (cookies) {
            cookies.add(cookie);
        }
    }

    public void addDateHeader(String name, long value) {
        if (isCommitted()) return;
//    if (included)
        //          return;     // Ignore any call from an included servlet
        addHeader(name, format.format(new Date(value)));
    }

    public void addHeader(String name, String value) {
        if (isCommitted()) return;
//        if (included)
        //          return;     // Ignore any call from an included servlet
        synchronized (headers) {
            ArrayList values = (ArrayList) headers.get(name);
            if (values == null) {
                values = new ArrayList();
                headers.put(name, values);
            }

            values.add(value);
        }
    }

    public void addIntHeader(String name, int value) {
        if (isCommitted()) return;
//    if (included)
        //    return;     // Ignore any call from an included servlet
        addHeader(name, "" + value);
    }

    public boolean containsHeader(String name) {
        synchronized (headers) {
            return (headers.get(name) != null);
        }
    }

    public String encodeRedirectURL(String url) {
        return null;
    }

    public String encodeRedirectUrl(String url) {
        return encodeRedirectURL(url);
    }

    public String encodeUrl(String url) {
        return encodeURL(url);
    }

    public String encodeURL(String url) {
        return null;
    }

    public void flushBuffer() throws IOException {
        //committed = true;
        if (bufferCount > 0) {
            try {
                output.write(buffer, 0, bufferCount);
            } finally {
                bufferCount = 0;
            }
        }
    }

    public int getBufferSize() {
        return 0;
    }

    public String getCharacterEncoding() {
        if (encoding == null) return ("ISO-8859-1");
        else return (encoding);
    }

    public Locale getLocale() {
        return null;
    }

    public ServletOutputStream getOutputStream() throws IOException {
        return null;
    }

    public PrintWriter getWriter() throws IOException {
        ResponseStream newStream = new ResponseStream(this);
        newStream.setCommit(false);
        OutputStreamWriter osr = new OutputStreamWriter(newStream, getCharacterEncoding());
        writer = new ResponseWriter(osr);
        return writer;
    }

    /**
     * Has the output of this response already been committed?
     */
    public boolean isCommitted() {
        return (committed);
    }

    public void reset() {
    }

    public void resetBuffer() {
    }

    public void sendError(int sc) throws IOException {
    }

    public void sendError(int sc, String message) throws IOException {
    }

    public void sendRedirect(String location) throws IOException {
    }

    public void setBufferSize(int size) {
    }

    public void setContentLength(int length) {
        if (isCommitted()) return;
//    if (included)
        //     return;     // Ignore any call from an included servlet
        this.contentLength = length;
    }

    public void setContentType(String type) {
    }

    public void setDateHeader(String name, long value) {
        if (isCommitted()) return;
//    if (included)
        //    return;     // Ignore any call from an included servlet
        setHeader(name, format.format(new Date(value)));
    }

    public void setHeader(String name, String value) {
        if (isCommitted()) return;
//    if (included)
        //    return;     // Ignore any call from an included servlet
        ArrayList values = new ArrayList();
        values.add(value);
        synchronized (headers) {
            headers.put(name, values);
        }
        String match = name.toLowerCase();
        if (match.equals("content-length")) {
            int contentLength = -1;
            try {
                contentLength = Integer.parseInt(value);
            } catch (NumberFormatException e) {
                ;
            }
            if (contentLength >= 0) setContentLength(contentLength);
        } else if (match.equals("content-type")) {
            setContentType(value);
        }
    }

    public void setIntHeader(String name, int value) {
        if (isCommitted()) return;
        //if (included)
        //return;     // Ignore any call from an included servlet
        setHeader(name, "" + value);
    }

    public void setLocale(Locale locale) {
        if (isCommitted()) return;
        //if (included)
        //return;     // Ignore any call from an included servlet

        // super.setLocale(locale);
        String language = locale.getLanguage();
        if ((language != null) && (language.length() > 0)) {
            String country = locale.getCountry();
            StringBuffer value = new StringBuffer(language);
            if ((country != null) && (country.length() > 0)) {
                value.append('-');
                value.append(country);
            }
            setHeader("Content-Language", value.toString());
        }
    }

    public void setStatus(int sc) {
    }

    public void setStatus(int sc, String message) {
    }
}

HttpRequestFacade与HttpResponseFacade

两个外观类,

HttpRequestFacade是HttpRequest的外观类,同样实现了HttpServletRequest接口,负责给servlet暴露HttpServletRequest接口方法的实现。

HttpResponseFacade是HttpResponse的外观类,同样实现了HttpServletResponse接口,负责给servlet暴露HttpServletResponse接口方法的实现。

HTTP连接处理类-HttpProcessor

讲了好几个HTTP请求与相应相关的类,终于轮到HttpProcessor了,前面讲了:HttpConnector只负责接收http请求的消息,具体的处理流程交给HttpProcessor来做。所以这个类的职责是:将http请求的请求行与请求头解析出来,并封装成HttpRequest与HttpResponse对象,然后交给serlvet容器。

这个类的主要复杂点在于这两行内容

parseRequest方法负责解析请求行的内容,将method、uri、protocol、queryString解析出来,如果uri中包含jsessionid的话,将jsessionid也解析出来。

带jsessionid的请求url大概长这个样子http://localhost:8080/user/login.jsp;jsessionid=CA0CA7E455535994E523B01357B42214?xxxx=xxx

parseHeaders方法负责将HTTP请求中的请求头解析出来,放到 protected HashMap headers = new HashMap(); 这个属性里。如果检测到了请求头中有cookie信息,将其取出来往 protected ArrayList cookies = new ArrayList(); 这个属性里放一份。另外 content-length、content-type请求头的值也单独取出来放到了HttpRequest的 contentLength、contentType字段里。

由于InputStream流只能从头读到尾,所以 parseRequest、parseHeaders 的先后顺序不能反。而body体是否读取,就看servlet中是否需要了。

HttpProcessor代码如下

package ex03.hml.connector.http;

import ex03.hml.ServletProcessor;
import ex03.hml.StaticResourceProcessor;
import org.apache.catalina.util.RequestUtil;
import org.apache.catalina.util.StringManager;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

/**
 * 这个类被用来处理具体的某个http请求
 */
public class HttpProcessor {

    public HttpProcessor(HttpConnector connector) {
        this.connector = connector;
    }

    /**
     * 与调用它的 HttpConnector 做一个关联,但是这个属性暂时没用
     */
    private HttpConnector connector = null;
    
    private HttpRequest request;
    private HttpRequestLine requestLine = new HttpRequestLine();
    private HttpResponse response;

    // 下面这两个属性也暂时没用
    protected String method = null;
    protected String queryString = null;

    /**
     * 这是当前包的 StringManager
     */
    protected StringManager sm = StringManager.getManager("ex03.hml.connector.http");

    /**
     * 处理http请求
     */
    public void process(Socket socket) {
        SocketInputStream input;
        OutputStream output;
        try {
            input = new SocketInputStream(socket.getInputStream(), 2048);
            output = socket.getOutputStream();

            // 构建 HttpRequest,HttpResponse对象
            request = new HttpRequest(input);
            response = new HttpResponse(output);
            response.setRequest(request);
            response.setHeader("Server", "hml Servlet Container");

            // 解析请求行内容(HTTP请求的第一行内容),填充进request对象
            parseRequest(input, output);
            // 解析请求头,填充进request对象
            parseHeaders(input);

            //判断请求的是静态资源还是servlet,servlet请求格式为 /servlet/servletName
            if (request.getRequestURI().startsWith("/servlet/")) {
                ServletProcessor processor = new ServletProcessor();
                processor.process(request, response);
            } else {
                StaticResourceProcessor processor = new StaticResourceProcessor();
                processor.process(request, response);
            }

            // 关闭 socket
            socket.close();
        } catch (Exception e) {
            // 此http请求处理如果出现了问题,进行异常捕获,不影响下一个http请求的处理
            e.printStackTrace();
        }
    }

    /**
     * 本方法是org.apache.catalina.connector.http.HttpProcessor中类似方法的简化版。
     * 但是,此方法只解析一些“简单”的头文件,例如
     * "cookie"、"content-length"和"content-type",忽略其他报头
     */
    private void parseHeaders(SocketInputStream input) throws IOException, ServletException {
        while (true) {
            HttpHeader header = new HttpHeader();

            // 读取下一个header
            input.readHeader(header);
            if (header.nameEnd == 0) {
                if (header.valueEnd == 0) {
                    return;
                } else {
                    throw new ServletException(sm.getString("httpProcessor.parseHeaders.colon"));
                }
            }

            String name = new String(header.name, 0, header.nameEnd);
            String value = new String(header.value, 0, header.valueEnd);
            request.addHeader(name, value);
            // do something for some headers, ignore others.
            if (name.equals("cookie")) {
                // 解析出所有cookie
                Cookie cookies[] = RequestUtil.parseCookieHeader(value);
                for (int i = 0; i < cookies.length; i++) {
                    if (cookies[i].getName().equals("jsessionid")) {
                        // Override anything requested in the URL
                        if (!request.isRequestedSessionIdFromCookie()) {
                            // Accept only the first session id cookie
                            request.setRequestedSessionId(cookies[i].getValue());
                            request.setRequestedSessionCookie(true);
                            request.setRequestedSessionURL(false);
                        }
                    }
                    request.addCookie(cookies[i]);
                }
            } else if (name.equals("content-length")) {
                int n = -1;
                try {
                    n = Integer.parseInt(value);
                } catch (Exception e) {
                    throw new ServletException(sm.getString("httpProcessor.parseHeaders.contentLength"));
                }
                request.setContentLength(n);
            } else if (name.equals("content-type")) {
                request.setContentType(value);
            }
        } //end while
    }


    /**
     * 这个方法解析SocketInputStream获取请求行内容(即HTTP请求第一行)
     * 包括:queryString、method、protocol、uri。如果uri中包含jsessionid的话,同时也罢jsessionid解析出来
     */
    private void parseRequest(SocketInputStream input, OutputStream output)
            throws IOException, ServletException {

        // 从input流中解析出请求行
        input.readRequestLine(requestLine);
        String method = new String(requestLine.method, 0, requestLine.methodEnd);
        String uri = null;
        String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd);

        // 校验 request line
        if (method.length() < 1) {
            throw new ServletException("Missing HTTP request method");
        } else if (requestLine.uriEnd < 1) {
            throw new ServletException("Missing HTTP request URI");
        }
        // 判断URI中存不存在query parameters,并解析出真正的URI
        int question = requestLine.indexOf("?");
        if (question >= 0) {
            request.setQueryString(new String(requestLine.uri, question + 1, requestLine.uriEnd - question - 1));
            uri = new String(requestLine.uri, 0, question);
        } else {
            request.setQueryString(null);
            uri = new String(requestLine.uri, 0, requestLine.uriEnd);
        }


        // 判断URI是不是绝对路径中的值 (带HTTP协议头的,例如:http://www.brainysoftware.com/index.html?name=Tarzan)
        if (!uri.startsWith("/")) {
            int pos = uri.indexOf("://");
            // 将协议和 host name 移除出去
            if (pos != -1) {
                pos = uri.indexOf('/', pos + 3);
                if (pos == -1) {
                    uri = "";
                } else {
                    uri = uri.substring(pos);
                }
            }
        }

        // 如果URI中包含jsessionid则将其解析出来,例如:http://localhost:8080/user/login.jsp;jsessionid=CA0CA7E455535994E523B01357B42214?xxxx=xxx
        String match = ";jsessionid=";
        int semicolon = uri.indexOf(match);
        if (semicolon >= 0) {
            String rest = uri.substring(semicolon + match.length());
            int semicolon2 = rest.indexOf(';');
            if (semicolon2 >= 0) {
                request.setRequestedSessionId(rest.substring(0, semicolon2));
                rest = rest.substring(semicolon2);
            } else {
                request.setRequestedSessionId(rest);
                rest = "";
            }
            request.setRequestedSessionURL(true);
            uri = uri.substring(0, semicolon) + rest;
        } else {
            request.setRequestedSessionId(null);
            request.setRequestedSessionURL(false);
        }

        // 标准化 URI,对非正常的URI进行修正
        String normalizedUri = normalize(uri);

        // Set 正确的请求参数
        request.setMethod(method);
        request.setProtocol(protocol);
        if (normalizedUri != null) {
            request.setRequestURI(normalizedUri);
        } else {
            request.setRequestURI(uri);
        }

        if (normalizedUri == null) {
            throw new ServletException("Invalid URI: " + uri + "'");
        }
    }

    /**
     * Return a context-relative path, beginning with a "/", that represents
     * the canonical version of the specified path after ".." and "." elements
     * are resolved out.  If the specified path attempts to go outside the
     * boundaries of the current context (i.e. too many ".." path elements
     * are present), return <code>null</code> instead.
     *
     * @param path Path to be normalized
     */
    protected String normalize(String path) {
        if (path == null)
            return null;
        // Create a place for the normalized path
        String normalized = path;

        // Normalize "/%7E" and "/%7e" at the beginning to "/~"
        if (normalized.startsWith("/%7E") || normalized.startsWith("/%7e"))
            normalized = "/~" + normalized.substring(4);

        // Prevent encoding '%', '/', '.' and '\', which are special reserved
        // characters
        if ((normalized.indexOf("%25") >= 0)
                || (normalized.indexOf("%2F") >= 0)
                || (normalized.indexOf("%2E") >= 0)
                || (normalized.indexOf("%5C") >= 0)
                || (normalized.indexOf("%2f") >= 0)
                || (normalized.indexOf("%2e") >= 0)
                || (normalized.indexOf("%5c") >= 0)) {
            return null;
        }

        if (normalized.equals("/."))
            return "/";

        // Normalize the slashes and add leading slash if necessary
        if (normalized.indexOf('\\') >= 0)
            normalized = normalized.replace('\\', '/');
        if (!normalized.startsWith("/"))
            normalized = "/" + normalized;

        // Resolve occurrences of "//" in the normalized path
        while (true) {
            int index = normalized.indexOf("//");
            if (index < 0)
                break;
            normalized = normalized.substring(0, index) + normalized.substring(index + 1);
        }

        // Resolve occurrences of "/./" in the normalized path
        while (true) {
            int index = normalized.indexOf("/./");
            if (index < 0)
                break;
            normalized = normalized.substring(0, index) + normalized.substring(index + 2);
        }

        // Resolve occurrences of "/../" in the normalized path
        while (true) {
            int index = normalized.indexOf("/../");
            if (index < 0)
                break;
            if (index == 0)
                return (null);  // Trying to go outside our context
            int index2 = normalized.lastIndexOf('/', index - 1);
            normalized = normalized.substring(0, index2) + normalized.substring(index + 3);
        }

        // Declare occurrences of "/..." (three or more dots) to be invalid
        // (on some Windows platforms this walks the directory tree!!!)
        if (normalized.indexOf("/...") >= 0)
            return (null);

        // Return the normalized path that we have completed
        return (normalized);

    }

}

servlet容器类-ServletProcessor

ServletProcessor的方法逻辑没有变化,仍然是先获取类加载器,然后加载servlet类,反射创建指定的servlet对象,创建HttpRequest与HttpResponse的门面类作为参数,调用servlet的service方法。

package ex03.hml;

import ex03.hml.connector.http.*;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;

public class ServletProcessor {

    public void process(HttpRequest request, HttpResponse response) {
        try {
            String uri = request.getRequestURI();
            String servletName = uri.substring(uri.lastIndexOf("/") + 1);

            //首先获取类加载器
            File file = new File(Constants.WEB_ROOT);
            String repository = (new URL("file", null, file.getCanonicalPath() + File.separator)).toString();
            URL[] urls = new URL[1];
            urls[0] = new URL(null, repository);
            URLClassLoader urlClassLoader = new URLClassLoader(urls);

            //加载servlet对应的类
            Class<?> aClass = urlClassLoader.loadClass(servletName);
            Servlet servlet = (Servlet) aClass.newInstance();

            HttpRequestFacade requestFacade = new HttpRequestFacade(request);
            HttpResponseFacade responseFacade = new HttpResponseFacade(response);

            servlet.service(requestFacade, responseFacade);
            response.finishResponse();

        } catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException |
                 ServletException e) {
            e.printStackTrace();
        }

    }

}

StaticResourceProcessor类

静态资源处理类,一如既往的简单,处理静态资源的逻辑仍然放到了HttpResponse类中实现

package ex03.hml;

import ex03.hml.connector.http.HttpRequest;
import ex03.hml.connector.http.HttpResponse;

public class StaticResourceProcessor {

    public void process(HttpRequest request, HttpResponse response) {
        response.sendStaticResource();
    }

}

Servlet具体实现类

除了上一章讲到的PrimitiveServlet外,本章引入一个新的servlet:ModernServlet,这个servlet中以html形式,将Http请求的一些信息展现了出来。

原书中的ModernServlet有一个坑点,那就是HTTP响应内容,使用了 Transfer-Encoding: chunked 分块传输的形式,但是却没有返回数据块的长度,导致返回结果无法解析,这里我将一并将它修复了。

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

public class ModernServlet extends HttpServlet {

    public void init(ServletConfig config) {
        System.out.println("ModernServlet -- init");
    }

    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
            throws ServletException, IOException {

        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        //先输出HTTP的头部信息  
        String msg = "HTTP/1.1 200 OK\r\n" +
                "Content-Type: text/html\r\n" +
                "Transfer-Encoding: chunked\r\n" +
                "\r\n";
        out.print(msg);

        StringBuilder builder = new StringBuilder();
        //再输出HTTP的消息体
        builder.append("<html>");
        builder.append("<head>");
        builder.append("<title>Modern Servlet</title>");
        builder.append("</head>");
        builder.append("<body>");

        builder.append("<h2>Headers</h2>");
        Enumeration headers = request.getHeaderNames();
        while (headers.hasMoreElements()) {
            String header = (String) headers.nextElement();
            builder.append("<br>" + header + " : " + request.getHeader(header));
        }

        builder.append("<br><h2>Method</h2>");
        builder.append("<br>" + request.getMethod());

        builder.append("<br><h2>Parameters</h2>");
        Enumeration parameters = request.getParameterNames();
        while (parameters.hasMoreElements()) {
            String parameter = (String) parameters.nextElement();
            builder.append("<br>" + parameter + " : " + request.getParameter(parameter));
        }

        builder.append("<br><h2>Query String</h2>");
        builder.append("<br>" + request.getQueryString());

        builder.append("<br><h2>Request URI</h2>");
        builder.append("<br>" + request.getRequestURI());

        builder.append("</body>");
        builder.append("</html>");

        // 这里是与原书中代码不一样的地方,原代码没有加chunked块的长度,浏览器不能正常解析
        out.print(Integer.toHexString(builder.length()) + "\r\n");
        out.print(builder.toString() + "\r\n");

        out.print("0\r\n\r\n");
        out.flush();
        out.close();
    }
}

运行结果展示

请求动态资源

请求静态资源

OK,以上就是本章的程序设计。截止到这章的内容,我们的Web容器仍然是运行在单线程模式下,只能挨个按顺序处理客户端的HTTP请求。什么时候开始支持并发呢?敬请期待下一章

源码分享

https://gitee.com/huo-ming-lu/HowTomcatWorks

本章代码在ex03包下

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/556137.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

docker 启动时报错

docker 启动时报如下错误 Job for docker.service failed because the control process exited with error code. See "systemctl status docker.service" and "journalctl -xe" for details 因为安装docker时添加了镜像源 解决方案&#xff1a; mv /etc/…

学习部分排序,插入排序,冒泡排序以及希尔排序

1.插入排序 <1>.首先我们举个例子 我们要把6进行前面的插入&#xff0c;那我们要进行比较&#xff0c;首先确定一个end的指针&#xff0c;然后他指向的数字就是我们需要比较的&#xff0c;如果end指向的数比我们end1 的大的话&#xff0c;那我们就往前挪一个&#xff0c…

ElasticSearch虚拟机安装(单机版)

1.下载7.10.2 下载链接&#xff0c;选择LINUX X86_64下载 2.创建用户 useradd es也可以使用系统默认用户&#xff08;非root&#xff09;,root用户会报错 3.解压 tar xvf elasticsearch-7.10.2-linux-x86_64.tar.gz假定目录在/home/es/elasticsearch-7.10.2-linux-x86_64 …

读所罗门的密码笔记21_读后总结与感想兼导读

1. 基本信息 所罗门的密码&#xff1a;AI时代的价值、权力与信任 Solomons Code 奥拉夫格罗思 马克尼兹伯格 著 中信出版社,2022年5月出版 1.1. 读薄率 书籍总字数257千字&#xff0c;笔记总字数37780字。 读薄率37780257000≈14.7% 1.2. 读厚方向 千脑智能 脑机穿越 …

Java垃圾回收1

1.对象什么时候可以被垃圾器回收 1.垃圾回收的概念 为了让程序员更专注于代码的实现&#xff0c;而不用过多的考虑内存释放的问题&#xff0c;所以&#xff0c; 在Java语言中&#xff0c;有了自动的垃圾回收机制&#xff0c;也就是我们熟悉的GC(Garbage Collection)。 有了垃圾…

python中的守护进程、僵尸进程、孤儿进程

继续上一篇文章的探讨&#xff1a;https://blog.csdn.net/weixin_39743356/article/details/137885419 守护进程 守护进程&#xff08;Daemon Process&#xff09;是一种在后台运行的特殊类型的进程&#xff0c;它独立于控制终端&#xff0c;并且周期性地执行某种任务或等待处…

本地部署 Meta Llama3-8b 和 Llama3-70b

本地部署 Meta Llama3-8b 和 Llama3-70b 0. 引言1. Meta对Llama 3的目标2. Llama 3的性能3. 下载和安装 Ollama4. 使用 Ollama 运行 Llama3 0. 引言 今天&#xff0c;Meta 正式介绍Meta Llama 3&#xff0c;Meta 开源大型语言模型的下一代产品。 这次发布包括具有80亿&#xf…

数据可视化(四):Pandas技术的高级操作案例,豆瓣电影数据也能轻松分析!

Tips&#xff1a;"分享是快乐的源泉&#x1f4a7;&#xff0c;在我的博客里&#xff0c;不仅有知识的海洋&#x1f30a;&#xff0c;还有满满的正能量加持&#x1f4aa;&#xff0c;快来和我一起分享这份快乐吧&#x1f60a;&#xff01; 喜欢我的博客的话&#xff0c;记得…

typecho博客的相对地址实现

typecho其中的博客地址,必须写上绝对地址,否则在迁移网址的时候会出现问题,例如页面记载异常 修改其中的 typecho\var\Widget\Options\General.php 中的165行左右, /** 站点地址 */if (!defined(__TYPECHO_SITE_URL__)) {$siteUrl new Form\Element\Text(siteUrl,null,$this-…

Tomcat和Spring Boot配置https

生成测试证书 生成证书前&#xff0c;先验证本地是否正确配置jdk环境变量&#xff0c;如果jdk环境变量配置正确&#xff0c;在命令行程序输入生成证书的命令。 keytool -genkey -alias tomcat -keyalg RSA -keystore "F:\job\apache-tomcat-8.5.29\key\freeHttps.keysto…

MySQL模糊查询

一、MySQL通配符模糊查询(%&#xff0c;_) 1.1.通配符的分类 1.“%”百分号通配符&#xff1a;表示任何字符出现任意次数&#xff08;可以是0次&#xff09; 2.“_”下划线通配符&#xff1a;表示只能匹配单个字符&#xff0c;不能多也不能少&#xff0c;就是一个字符。当然…

Yoshua Bengio独家专访:我不想把大模型未来押注在Scaling Law上,AGI路上要“注意安全”...

导读 漫长的30年间&#xff0c;数度从主流方向的超然出走&#xff0c;是Bengio的制胜秘诀。这种不盲从主流的风格体现在他研究生涯的方方面面。 90年代末期&#xff0c;神经网络被打入冷宫&#xff0c;Bengio的论文多次遭拒&#xff0c;连学生们也开始担心&#xff0c;和他一起…

EPSON晶振应用到汽车电子产品上的型号有哪些?

EPSON品牌应用在汽车电子产品上的晶振.&#xff0c;当然也少不了晶振可能最熟悉的就是32.768K系列和26MHZGPS晶振用的多。 在汽车里每一个部件都应有的不一样,甚至多次使用到同一尺寸,不同频率的晶振.爱普生品牌晶振型号就有几百种,很容易混淆,要想记住汽车里所应用到的不是件…

⑥【Shiro】使多个自定义Realm规则生效。

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ ⑥【Shiro】Shiro中&#xff0c;如何使多个自定…

DevOps(七)Jenkins发布第一个流水线任务

Jenkins的流水线&#xff08;Pipeline&#xff09;是一种强大的工具&#xff0c;用于定义和管理持续集成和持续交付&#xff08;CI/CD&#xff09;过程。它允许你以代码的形式&#xff08;即"Pipeline as Code"&#xff09;定义整个构建、测试和部署流程&#xff0c;…

UE4 拍摄、保存并浏览相册

效果&#xff1a; 1.新建CameraActor类 2.修改截图保存路径 3.编写BP_Camera蓝图 注意路径 Save Image函数要在执行拍照和BeginPlay事件执行一次 按钮执行拍摄事件 3.编写UMG蓝图 技巧&#xff1a;让Index加1、减1循环赋值 4.把BP_Camera挂在玩家上

《QT实用小工具·三十》基于QT开发的访客管理平台demo

1、概述 源码放在文章末尾 该项目为访客管理平台demo&#xff0c;包含主界面、系统设置、警情查询、调试帮助、用户退出功能。 项目部分代码如下&#xff1a; #ifndef QTHELPER_H #define QTHELPER_H#include "head.h"class QtHelper { public://获取所有屏幕区域…

从Linux角度具体理解程序翻译过程-----预处理、编译、汇编、链接

前言&#xff1a; 在C语言中&#xff0c;我们知道程序从我们所写的代码到可执行执行的过程中经历了以下过程 1.预处理 2.编译 3.汇编 4.链接 可以通过下图来理解 翻译过程 1.预处理 该过程主要进行以下操作&#xff1a; (1)头文件的包含 (2)define定义符号的替换&#xff…

怎样实现opc采集数据后传给web后端

现在很多老工厂要进行数字化改造&#xff0c;现场生产的各种数据需要传到web后端&#xff0c;很多工厂现场原有的自动监控系统已经采集了现场的各种数据&#xff0c;只是没有形成联网。如果前端自动化系统全部废除&#xff0c;重新做数字化控制系统&#xff0c;成本投入太大&am…

docker服务无法启动

背景&#xff1a;断电重启经常会导致磁盘io错误&#xff0c;甚至出现磁盘坏块 这时可以使用xfs_repair来修复磁盘&#xff0c;但是修复过程可能会导致部分数据丢失 xfs_repair -f -L /dev/sdc问题一&#xff1a; Apr 15 19:27:15 Centos7.6 systemd[1]: Unit docker.service e…