LOADING

加载过慢请开启缓存 浏览器默认开启

2023/11/18

java(web编程、servlet与tomcat)

学完TCP编程后,web编程只是对其的一个扩展,servlet封装了一些web编程接口,使得处理web编程更简单

inputstream/outputstream本质是创建一个输入/输出流连接,连接是持续的,每次输入都会进入输入流中,不断read即可

inputstream每次读入8位的2进制数,也就是一个字节的数据,并将8位二进制数转为整数(0-255),inputstreamreader只不过多了一个编码方式,会把读入的二进制整数以固定编码方式解码

maven结构是固定的,maven项目构建(打包)过程会对src/main/java文件下的源代码进行打包

a-maven-project
├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   └── resources
│   └── test
│       ├── java
│       └── resources
└── target
唯一确定一个maven项目的3要素:
组织名称、项目名、版本
<groupId>org.example</groupId>
<artifactId>demo_web</artifactId>
<version>1.0-SNAPSHOT</version>

<packaging>war</packaging>  <!--以war形式打包而不是jar,表示这是一个Java Web Application Archive-->
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class HTTPserver {
    public static void main(String[] args) throws IOException {
        ServerSocket ss=new ServerSocket(5050);
        System.out.println("Server is running...");
        for(;;){
            Socket socket=ss.accept(); //接收一个远程socket端口,建立TCP连接   这一步如果没接收到socket会一直等待下去
            System.out.println("connected from "+socket.getRemoteSocketAddress());
            Thread handle_thread=new Handler(socket);
            handle_thread.start();
        }
    }
}
class Handler extends Thread{
    Socket socket;
    public Handler(Socket socket){
        this.socket=socket;
    }

    @Override
    public void run(){
        //当 try 后面使用括号时,括号内声明的资源(例如文件、网络连接等)会在代码块执行完毕后自动关闭,
        // 无需手动调用资源的关闭方法(比如 close())。
        // 这种形式被称为 "try-with-resources",它可以确保在代码执行结束后资源被正确释放,从而避免资源泄露。
        try (InputStream input=socket.getInputStream()){  //InputStream创建的实际上是一个输入流连接!连接的内容会随着后续输入自动传递到input中,随着你下一次read读取而得到
            try (OutputStream output=socket.getOutputStream()){
                handle(input,output);
            }
        }catch (Exception e){
        }finally {
            try{
                socket.close();
            }catch (Exception e){}
        System.out.println("client disconnected");
        }
    }

    public void handle(InputStream input,OutputStream output) throws IOException{   //InputStream每次读取8位二进制数据(也就是一个字节的数据),并把8位二进制数据表示成整数0-255范围
        var reader =new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));  //InputStreamReader自动把读入的字节数据转为UTF8编码后结果
        var writer =new BufferedWriter(new OutputStreamWriter(output,StandardCharsets.UTF_8));  //Buffered自动创建缓冲区(一个字节数组)
        String header_first=reader.readLine();
        System.out.println(header_first);
        boolean requestok=false;
        if(header_first.startsWith("GET / HTTP/1.")){
            requestok=true;
        }
        for(;;){
            String header=reader.readLine();
            if (header.isEmpty()) { // 读取到空行时, HTTP Header读取完毕
                break;
            }
            System.out.println(header);
        }
        if(!requestok){
            writer.write("HTTP/1.0 404 Not Found");
            writer.write("Content-Length: 0\r\n");
            writer.write("\r\n");
            writer.flush();
        }else {
            String index_html="<html><body><h1>Hello, world!</h1></body></html>";
            int length=index_html.getBytes(StandardCharsets.UTF_8).length;
            writer.write("HTTP/1.0 200 \r\n");  //必须按照形式打印出HTTP/1.0 200 才能让浏览器正确响应(404也可以输出内容,但浏览器网络IO会爆红)
            writer.write("Connection: close\r\n"); ////往输出缓存区里写东西
            writer.write("Content-Type: text/html\r\n");
            writer.write("Content-Length: " + length + "\r\n");
            writer.write("\r\n"); // 空行标识Header和Body的分隔
            writer.write(index_html);
            writer.flush();
        }
    }
}
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

/*  maven打包与IDEA使用坑

!!IDEA新建的index.jsp实际上是个html文件,其固定返回hello,world!页面,他是个索引页面,访问任何servlet都会先只返回index.html

通常情况下,Servlet 的源代码应该放在 Maven Web 项目的 "src/main/java" 目录下的相应包结构中。
当你将 Servlet 源代码放置在正确的位置后,Maven 会在构建过程中自动将这些 Java 源文件编译为字节码,
并将编译后的类文件打包到最终的 WAR 文件中。
然而,Servlet 类本身并不直接存在于 Web 应用程序的目录结构中。Servlet 类是一个 Java 类,
它在被部署到 Servlet 容器时会由容器进行管理和执行。因此,你不需要将 Servlet 源代码放在 "src/main/webapp" 目录中才能使其被打包。
总结来说,将 Servlet 的源代码放置在 "src/main/java" 目录下的正确包结构中,
并确保 Maven 正确地编译和打包你的项目,Servlet 类就能被正确地部署到 Servlet 容器中。
*/


@WebServlet(urlPatterns = "/")  //映射到/的IndexServlet比较特殊,它实际上会接收所有未匹配的路径,相当于/*
public class HTTPserver_servlet extends HttpServlet {
    /*
    一个Servlet总是继承自HttpServlet,然后覆写doGet()或doPost()方法。
    注意到doGet()方法传入了HttpServletRequest和HttpServletResponse两个对象,分别代表HTTP请求和响应。
    我们使用Servlet API时,并不直接与底层TCP交互,也不需要解析HTTP协议,
    因为HttpServletRequest和HttpServletResponse就已经封装好了请求和响应。
    以发送响应为例,我们只需要设置正确的响应类型,然后获取PrintWriter,写入响应即可。
    * */
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        //一个Servlet类在服务器中只有一个实例,但对于每个HTTP请求,Web服务器会使用多线程执行请求。
        // 因此,一个Servlet的doGet()、doPost()等处理请求的方法是多线程并发执行的。
        // 如果Servlet中定义了字段,要注意多线程并发访问的问题(如果字段的更改有要求,要记得加锁!)
        String name=request.getParameter("name");
        String redirect_url="/"+"demo_web-1.0-SNAPSHOT/"+(name==null?"":name);
        //response.sendRedirect(redirect_url);  将重定向请求导入到当前servlet后可能导致无限重定向循环!
        // 接收到重定向请求后,浏览器并不会执行后续内容,而是重新根据返回的Location再发一个请求,一般用于web应用升级
        response.setHeader("ContentType","text/html");
        PrintWriter printWriter=response.getWriter();
        if(name!=null){
            printWriter.write("<h1>Hello, %s!</h1>".formatted(name));
            printWriter.flush();
        }
        else {
            printWriter.write("<h1>Hello, 无名氏!</h1>");
            printWriter.flush(); //但是,写入完毕后调用flush()却是必须的,因为大部分Web服务器都基于HTTP/1.1协议,会复用TCP连接。
            // 如果没有调用flush(),将导致缓冲区的内容无法及时发送到客户端。
            // 此外,写入完毕后千万不要调用close(),原因同样是因为会复用TCP连接,
            // 如果关闭写入流,将关闭TCP连接,使得Web服务器无法复用此TCP连接。
        }
    }

    //想运行编写的servlet容器快速处理请求的时候,必须打包成war包并运行在tomcat服务器上

    //实际上,类似Tomcat这样的服务器也是Java编写的,
    // 启动Tomcat服务器实际上是启动Java虚拟机,执行Tomcat的main()方法,
    // 然后由Tomcat负责加载我们的.war文件,并创建一个HelloServlet实例,最后以多线程的模式来处理HTTP请求。
    // 如果Tomcat服务器收到的请求路径是/(假定部署文件为ROOT.war),
    // 就转发到HelloServlet并传入HttpServletRequest和HttpServletResponse两个对象。
}