JavaWeb--Servlet(上)

前言

加油加油!!

一丶Servlet是什么

这样说,一个web服务器提供了web程序,用来处理服务端对请求的解析,以及响应的封装,这样就不需要我们自己写程序来完成对应的功能了。但是这样就会有一个问题。
假如当我们自己写一个web应用,然后把这个webapp部署到web服务器tomcat的时候,就会调用tomcat的API,也就是使用tomcat提供的类和接口中的方法或者属性。但是如果我们不使用tomcat,那么这个产品就无法使用了。
这个时候,就是我们Servlet存在的意义了。

Servlet为不同的JavaWeb服务器,规定了响应的编程规范(官方规范)。也就是说它会屏蔽web服务器实现的细节(不同的web服务器,可以是不同的对请求解析和响应封装的实现)。因为它规定了统一的编程规范(统一的类,接口,方法等),所以在我们换一个web服务器的时候,web应用程序还是可以用的。

所以Servlet可以做什么呢?

1.html是静态的资源文件,如果我想返回动态的网页内容,那么就可以使用html。
2.文件下载也可以使用Servlet,把静态文件放在tomcat/webapps下,访问就可以
下载。
3.用户登录也可以使用Servlet,在客户端浏览器输入账号密码,然后在服务端写程
序来校验账号密码,如果成功,就跳转到另一个页面,如果效验失败,那么就还是
在登录页面。

二丶Servlet的项目部署

<1>创建第一个Servlet程序

首先我们创建一个maven项目,这里我们就不演示怎么创建了,直接进入主题。
当我们创建成功之后,我们引入Servlet的依赖包,首先进入如下网址
引入Servlet的依赖包

之后在pom.xml文件添加如下语句引入Servlet的依赖包

<dependencies>
        <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
        <!-- Servlet依赖包:官方提供的servlet标准-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <!--开发编译时候需要这个依赖包,运行时候不需要-->
            <scope>provided</scope>
        </dependency>

编写完之后刷完maven面板,不然不会生效
在这里插入图片描述

接着我们创建web需要的目录和web.xml(描述/配置web的应用信息)
在这里插入图片描述
接着引入配置信息

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
    <display-name>Archetype Created Web Application</display-name>
</web-app>

然后我们就可以正式进行servlet的开发了。

<2>关于servlet程序的解释

在src路径的java包下创建一个类。这里我把类的名称设置为HelloServlet

第一步

类名称上面的内容添加

@WebServlet("")
webServlet表示当前类是一个servlet类,需要处理请求和响应。

那么我们要怎样知道哪一个请求是需要这个Servlet类来处理的?
那么就需要定义访问路径

第二步

@WebServlet("/hello")

注意,这里访问路径是"/"开头,不然就会报错。当然,这个虚拟路径,可能没有对应的文件。

第三步

接着我们就要处理HTTP协议啦,也就是继承HttpServlet类

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
    }
}

这里注意啦

HttpServletRequest:http请求封装的类型,使用这个对象就可以获取http请求报文的内容
HttpServletResponse:http响应封装的类型,使用这个对象就可以设置http响应报文的内容

第四步

然后我们重写父类的doXXX方法(XXX就是提供的服务方法,请求的时候,也要使用相同的方法)

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //    super.doGet(req, resp);  //这段代码一定要删掉,不然会报错
        resp.getWriter().print("hello World");
    }
}

<3>项目在tomcat的部署

然后我们在porn.xml中加入如下语句,使得我们的压缩格式是war格式

	<groupId>org.example</groupId>
    <artifactId>servlet-study</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!-- web应用需要打包为war格式,普通Java应用打包为jar -->
    <packaging>war</packaging>

然后把这个压缩文件复制到webapps目录下,也就是我们的tomcat文件目录下,接着运行tomcat,然后就会解压缩并且生成一个相同名称的文件夹
在这里插入图片描述
至此,我们的Servlet程序运行完毕。

<4>步骤的简化

不得不说,上述程序是有点繁琐了,所以有没有可以简化步骤的呢?
当然是有的,如下所示安装tomcat插件。

在这里插入图片描述
在这里插入图片描述
然后安装完之后进行配置
在这里插入图片描述

在这里插入图片描述

然后我们就可以直接在IDEA里面运行Tomcat了

在这里插入图片描述

三丶访问出错

<1>404

出现404表示用户访问的资源不存在,大概率是URL的路径不正确。
在这里插入图片描述
就像这里,我们如果我们不给访问路径加hello的话,那么url访问路径就是错误的,就会出现404.

<2>405

405方法不支持,什么意思呢?看如下代码

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //    super.doGet(req, resp);  //这段代码一定要删掉,不然会报错
        resp.getWriter().print("hello World");
    }
}

这里我们在浏览器输入url的时候,就会发送一个http的GET请求,所以当我们把这个doGet改为doPost的时候,因为没有实现doGet方法,就会出现如下情况。
在这里插入图片描述

<3>500

往往是 Servlet 代码中抛出异常导致的,这里指的是服务器内部出错。如果出现了这种情况,就一定要检查异常堆栈信息(页面上可能有,idea控制台,tomcat/logs当中的日志文件)
就比如这样举一个例子,看如下代码:

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        int i = 0;
        int j = 5 / 0;
        resp.getWriter().print(j);
    }
}

因为除数不能为0,这里就出现了500错误
在这里插入图片描述

<4>空白页面

这里咋说呢,这样演示吧

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("看这里");
    }
}

当我们运行这段代码的时候,可以发现没有报错,但是页面就是空白的,抓包也是空数据,这个就是空白页面。

<5>出现无法访问此页面

这里的话可能就是Servlet Path写错了,总的来说出现这种错误,在Tomcat启动的日志里找关键信息就好啦。

<6>总结

1.4xx 的状态码表示路径不存在, 往往需要检查 URL 是否正确, 和代码中设定的
Context Path 以及Servlet Path 是否一致. 
2.5xx 的状态码表示服务器出现错误, 往往需要观察页面提示的内容和 Tomcat 自身
的日志, 观察是否存在报错. 
3.出现连接失败往往意味着 Tomcat 没有正确启动, 也需要观察 Tomcat 的自身日
志是否有错误提示. 
4.空白页面这种情况则需要我们使用抓包工具来分析 HTTP 请求响应的具体交互
过程. 

四丶Servlet运行原理

1>接收请求

1.用户在浏览器输入一个URL此时客户端浏览器就会构造一个HTTP请求,这个请求会被网络协议栈逐层进行封装,然后变为二进制的bit流,最后通过物理层的硬件设备转成光信号/电信号传输出去

2.网络主机接收到这些光信号/电信号又会通过网络协议栈逐层解析,最后还原成HTTP请求交给Tomcat进程(根据端口号确定对应进程)处理。

3.Tomcat通过Socket读取到这个请求(一个字符串),并且按照HTTP请求的格式来解析这个请求,根据Content Path来确定一个webapp,再根据Servlet Path确定一个具体的类,再根据当前请求的方法(Post/Get),决定调用这个类的doGet或者doPost方法,此时我们代码当中的第一个参数HttpServletRequest 就包含了这个 HTTP 请求的详细信息。

具体图示如下:
在这里插入图片描述

2>根据请求计算响应

在我们的doGet/doPost方法当中,就执行到了我们自己的代码,我们自己的代码会根据请求当中的一些信息,来给HttpServletResponse对象设置一些属性,比如状态码,header,body等等。

3>返回响应

  1. doGet / doPost 执行完毕后, Tomcat 就会自动把 HttpServletResponse 这个我们刚设置好的对象转换成一个符合 HTTP 协议的字符串, 通过 Socket 把这个响应发送出去.

2.响应数据在服务器的主机上通过网络协议栈层层 封装, 最终又得到一个二进制的 bit 流, 通过物理层硬件设备转换成光信号/电信号传输出去

3.主机收到这些光信号/电信号, 又会通过网络协议栈逐层进行 分用, 层层解析, 最终还原成HTTP 响应, 并交给浏览器处理.

4.浏览器也通过 Socket 读到这个响应(一个字符串), 按照 HTTP 响应的格式来解析这个响应. 并且把body 中的数据按照一定的格式显示在浏览器的界面上

五丶Servlet API详解(上)

<1>HttpServletRequest–获取queryString

Tomcat通过Socket API读取HTTP请求(字符串),并且按照HTTP协议的格式,把字符串解析成为HttpServletRequest的对象。

这里就着重介绍一些重点的API,介绍方式还是一边写一遍介绍。

因为IEDA有些版本写前端代码时候没有高亮显示,所以这里我使用VSCode进行前端代码的书写。

首先在webapp文件目录下创建request.html文件
然后书写

<h3>get with query string</h3>
    <!--当前页面访问路径:/[contextPath应用上下文路径]/request.html,需要跳转到/[contextPath]/request-->
    <!--也可以在url输入链接跳转后相同的url绝对路径来访问-->
    <a href="request?username=abc&password=123">get</a>

这里通过url路径来进行访问的时候,就可以看到如下内容
在这里插入图片描述
点击get,就可以将这个queryString发送给我们的服务端。
然后我们在IDEA中书写我们的后端代码,来获取queryString当中包含的信息
这里就着重介绍一些我们常用的方法

1>getProtocol()请求协议名

这里是返回一个String,也就是返回请求协议的名称和版本

System.out.println("请求协议名:" + req.getProtocol());

2>getMethod()请求方法

返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT

System.out.println("请求方法:" + req.getMethod());

3>getContextPath()上下文路径

返回指示请求上下文的请求 URI 部分。

System.out.println("上下文路径/应用名:" + req.getContextPath());

4>getServletPath()资源路径/servletPath

返回资源路径

System.out.println("资源路径/servletPath:" + req.getServletPath());

5>getHeader(String name)

以字符串形式返回指定的请求头的值。

System.out.println("请求头中键为Host的值" + req.getHeader("Host"));

当我们在重写的doGet方法中完成一下内容之后,点击提交按钮,就可以获取queryString当中的信息。
具体代码如下:

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("请求协议名:" + req.getProtocol());
        System.out.println("请求方法:" + req.getMethod());
        System.out.println("上下文路径/应用名:" + req.getContextPath());
        System.out.println("资源路径/servletPath:" + req.getServletPath());
        System.out.println("请求头中键为Host的值" + req.getHeader("Host"));

        req.setCharacterEncoding("utf-8");//设置body解析的编码格式(表单提交的时候这里会有乱码)
        System.out.println("username:" + req.getParameter("username"));
        System.out.println("password:" + req.getParameter("password"));

        //响应给客户端的内容
        resp.getWriter().println("请求信息已获取,在idea控制台查看");
    }

这里要着重注意设置"utf-8",以防止乱码。提交完成后在控制台就能看到如下显示。
在这里插入图片描述

<2>HttpServletRequest–获取body

Get请求的正文内容是在queryString当中,但是Post是在body当中,所以如果我们要获取body当中的数据格式,我们要怎么办呢?
其实也就和上面的获取queryString的代码是一模一样的,但是在这里,我们就必须要重写doPost方法,然后在doPost方法内部发起对doGet方法的调用,这样就可以获取body的内容

 @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);//逻辑一样,就调用doGet就好
    }

<3>获取body–form表单格式提交

form表单提交在不进行特别声明的时候,默认是Get方法。

<form action="request">
        <input type="text" name="username" placeholder="输入用户名">
        <br>
        <input type="password" name="password" placeholder="输入密码">
        <br>
        <input type="submit" value="提交">
    </form>

POST的请求一般是通过body传递给服务器,但是他这个数据类型就有待推敲。如果说是表单格式,也就是form表单形式,那么我们还是可以通过上文里的getParameter 获取参数的值。
但是这里我们肯定是要设置一下数据的格式的!和上面不同的就是需要特别声明方法的类型

 <h3>表单格式:/request</h3>
    <form action="request" method="post">
        <input type="text" name="username" placeholder="输入用户名">
        <br>
        <input type="password" name="password" placeholder="输入密码">
        <br>
        <input type="submit" value="提交">
    </form>

所以这里对应显示如下:
在这里插入图片描述

<4>获取body-- form-data格式提交

<1>获取简单数据

form-data格式提交的时候需要特别注明请求数据的格式

enctype="multipart/form-data"

具体代码如下:

<h3>form-data格式:/form-data-servlet</h3>
    <form action="form-data-servlet"  enctype="multipart/form-data" method="post"><!--enctype是请求数据的格式-->
        <input type="text" name="username" placeholder="输入用户名">
        <br>
        <input type="password" name="password" placeholder="输入密码">
        <br>
        <input type="submit" value="提交">
    </form>

然后在IDEA中对body内容的获取,其实在这里也还是用getParameter()这个方法。

req.setCharacterEncoding("utf-8");//设置body解析的编码格式
System.out.println("username:" + req.getParameter("username"));
System.out.println("password:" + req.getParameter("password"));

<2>获取文件

当form-data格式上传文件的时候,我们要如何获取呢?这里我们就不能用getParameter()这个方法啦。
那么具体怎么做呢?

方 法 一 \color{red}{方法一}

//获取上传文件的二进制数据,转换为字符串打印
InputStream is = head.getInputStream();//获取输入流(里面包含了数据)
//了解:输入流.available返回包含的数据长度
byte[] bytes = new byte[is.available()];//获取长度
//从输入流中,把数据读取到byte[]中,字节数组对象就有了这些数据
is.read(bytes);
System.out.println(new String(bytes,"utf-8"));

之后我们就能够接收文件啦。

方 法 二 \color{red}{方法二}

第二种方法,我们可以直接获取头信息,然后通过头信息把客户端上传的文件保存在本地,在这个过程中,我们要使用getPart(String name)这个方法

		Part head = req.getPart("head");
        head.write("D://" + head.getSubmittedFileName());

这里要注意,如果说路径上已经有这个文件,那么再上传就会报错。

<5>获取body–json格式提交

如果说body是json格式提交,那么我们就要通过getInputStream获取请求正文的数据。当然这里不仅仅是json格式,不管Content-Type是什么类型的,都是可以获取到的。但是其他的一般都比较复杂,所以不推荐。这里json的使用使用这个方法比较典型的场景

<1>方法一 + 方法二

这里为什么直接两个方法放在一起呢?是因为这两种方法都不太推荐,获取整体信息还好说,那么如果说我们要单独获取username和password呢?
这就不太方便,所以这里这两种方法我们单单做一个了解就好。

		InputStream is = req.getInputStream();//输入流
        InputStreamReader isr = new InputStreamReader(is,"utf-8");    
        BufferedReader br = new BufferedReader(isr);
        StringBuilder sb = new StringBuilder();
        String str;
        while((str= br.readLine()) != null){
            sb.append(str);
        }

<2>方法三

这里我们要引入一个第三方库

import com.fasterxml.jackson.databind.ObjectMapper;

然后通过这个第三方库实现json字符串和java对象的相互转化。

 ObjectMapper mapper = new ObjectMapper();
 Map json = mapper.readValue(is, Map.class);
 System.out.println("获取的json字符串转换的map:" + json);

这种方式简单暴力,就直接转换为一个map对象,转换为Map对象是一个万金油的方法,任何json格式都可以转换为map。
但是!!!map使用也不方便,它使用指定的键来获取值,那么我们就需要再对这个方法进行改良,也就是加入一个静态内部类

static class User{
        private String username;
        private String password;
        //再提供getter 和 setter方法

        @Override
        public String toString() {
            return "User{" +
                    "username='" + username + '\'' +
                    ", password='" + password + '\'' +
                    '}';
        }

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

        public String getPssword() {
            return password;
        }

        public void setPssword(String pssword) {
            this.password = pssword;
        }
    }

然后之后在主类我们就不需要这么麻烦了

ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(is,User.class);
System.out.println("获取的json字符串转换的user对象:" + user);
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值