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两个对象。
}