HTTP协议
点击链接,服务器怎么知道要打开哪一个网页呢?答案是HTTP协议。在浏览器中单击链接的时候,浏览器会向服务器发送一段文本,告诉服务器请求的是那个网页。
GET方式提交数据:
HTTP头数据:
以Get方式访问/s?wd=Java 这个网站,用的是HTTP/1.0的协议。Get方式查询时,提交的查询内容Java是显示在浏览器地址栏中的,并且提交的网址不能超过256个字符。
其中User-Agent是浏览器信息,Accept是浏览器支持的格式,Cookie记录的是用户当前的状态,Referer是指从哪个页面点击链接进入/s?wd=Java页面的。
POST方式提交数据:
由于Get不能提交超过256个字符,于是大量的文本需要用Post来提交,如提交用户资料、上传文件。提交的数据不会在地址栏显示。
文本的,普通文本数据:application/x-www-form-urlencoded
非文本的多媒体数据,文件数据:multipart/form-data
总的来说,Get负责显示页面(以及传输少量数据),Post负责上传数据。
Servlet
在配置文件中,servlet的配置:
<servlet> 告诉servlet容器(如tomcat),我需要提供XX功能的servlet;
<servlet-mapping> 我可以通过哪个url访问这个servlet;
** servlet-name可以取任意字符串值,但在web.xml里必须唯一。
** servlet-name须与<servlet>元素中声明的名字一致。
Servlet工作流程
** 所有的HTTP头数据都可以通过request相应的方法查询到!所以说,servlet把HTTP的内容对象化了。
Servlet规范和Web访问方式
Java Web目录结构
Tomcat的/webapps是存放所有web部署的文件夹,里面所有的应用(文件夹)之间分别成为上下文路径(相对路径)
Web程序有固定的结构,必须按照这样的方式去部署:
编写Servlet
@WebServlet("/GetTimeServlet")
public class GetTimeServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public GetTimeServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.log("running doGet method...");
this.execute(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.log("running doPost method...");
//doGet(request, response); //显示用post方式提交,结果执行的时候还是用get方式提交,实际上两种方式都执行
this.execute(request, response);
}
public long getLastModified(HttpServletRequest request) {
this.log("running getLastModified method..");
return -1;
}
private void execute(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setCharacterEncoding("UTF-8");
request.setCharacterEncoding("UTF-8");
String requestURI=request.getRequestURI(); // http header envoloped in form of request
String method=request.getMethod();
String param=request.getParameter("param");
response.setContentType("text/html");
PrintWriter out=response.getWriter();
out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
out.println("<HTML>");
out.println("<HEAD><TITLE>A Servlet</TITLE></HEAD>");
out.println("<BODY>");
out.println("via method:"+method+", we have the param of:"+param+"<br/>");
out.println(" <form action='"+requestURI+"' method='get'><input type='text' name='param' value='param string'><input type='submit' value='request with GET"+requestURI+"'></form>");
out.println(" <form action='"+requestURI+"' method='post'><input type='text' name='param' value='param string'><input type='submit' value='request with POST"+requestURI+"'></form>");
out.println(" <script>document.write('last modified at: '+document.lastModified);</script>");
out.println("</BODY>");
out.println("</HTML>");
out.flush();
out.close();
}
}
配置<servlet>
<servlet>
<servlet-name>GetTimeServlet</servlet-name>
<servlet-class>com.integrate.GetTimeServlet</servlet-class>
</servlet>
此外,<servlet>还有一些可选的配置,如init-param(包含所有你想要初始化的变量),load-on-startup
配置<servlet-mapping>
** 配置好servlet名称与类名之后还需要配置servlet访问方式
<servlet-mapping>
<servlet-name>GetTimeServlet</servlet-name>
<url-pattern>/servlet/GetTimeServlet</url-pattern>
</servlet-mapping>
一个完整的Servlet包括Servlet类、<servlet>配置、<servlet-mapping>配置,缺一不可。
出来的网页:
能够根据表格<form>的调用方法method特性,去将数据传递到servlet从而实现处理输出。
其中采用get方法会在地址栏中显示参数,而post不会。
请求与响应
客户端浏览器发送了一个请求,服务器做出一系列操作后做出一个响应,发送给客户端,完成一次【Web过程】操作。
【Web编程】的过程就是通过请求分析客户需要什么信息或者进行了什么操作,然后进行一系列的处理,最后通过响应结果显示给客户。
Request的实例在Integrate/RequestServlet,Response的实例在Integrate/IdentityServlet,通过打开IdentityTest.html可以测试。
初始化参数Init-param
Servlet从配置文件中获取参数
因此我们只要用getInitParameter()就可以访问相应param-name的param-value了
// 只显示方法部分,答对了账号和密码才能登入
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.log("run doGet()...");
response.setCharacterEncoding("UTF-8");
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
out.println("<HTML>");
out.println("<HEAD><TITLE>Please log in to check File 'Notice' (Servlet)</TITLE></HEAD>");
out.println("<style>body, font, td, div{font-size:12px; line-height:18px;}</style>");
out.println("<BODY>");
out.println("<form action='"+request.getRequestURI()+"' method='post'>"); //上传表格还是要调用doPost方法的
out.println("please enter you account: <input type='text' name='username' style='width:200px'><br/>");
out.println("please enter you password: <input type='password' name='password' style='width:200px'><br/><br/>");
out.println("<input type='submit' value=' log in '/>");
out.println("</form>");
out.println();
out.println("</BODY>");
out.println("</HTML>");
out.flush();
out.close();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
Enumeration params = this.getInitParameterNames();
while(params.hasMoreElements()) {
String usernameParam=(String) params.nextElement();
String passnameParam=getInitParameter(usernameParam);
if(usernameParam.equalsIgnoreCase(username) && passnameParam.equals(password)) {
request.getRequestDispatcher("/WEB-INF/notice.html").forward(request, response);
return;
}
}
this.doGet(request, response);
}
上下文参数Context-Param
Init-Param配置在<servlet>里,只能够由该servlet来读取,不能被全局访问。Context-Param类似全局所有servlet都可访问的Init-Param。
ServletContext对象
产生:getServletConfig().getServletContext()
作用:使用getInitParameter()方法来获取指定名称的参数,使用getInitParameterNames()获取所有context-param参数名称
资源注射(@Resource)
通过注解,自动读取在web.xml里面的初始参数,运行时赋值
@Resource(name="messageNameInWebXml")
private String message;
也可写在同一行上
private @Resource(name="messageNameInWebXml") String message;
实例中,获取注射信息的代码
对应的配置文件
<servlet>
<servlet-name>InjectionServlet</servlet-name>
<servlet-class>com.integrate.InjectionServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>InjectionServlet</servlet-name>
<url-pattern>/servlet/InjectionServlet</url-pattern>
</servlet-mapping>
<!-- 资源配置 -->
<env-entry>
<env-entry-name>hello</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>Hello, welcome to the JavaEE Resource Injection...</env-entry-value>
</env-entry>
<env-entry>
<env-entry-name>i</env-entry-name>
<env-entry-type>java.lang.Integer</env-entry-type>
<env-entry-value>31</env-entry-value>
</env-entry>
<env-entry>
<env-entry-name>persons</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>Helloween, Cobain, Roses, Axl, </env-entry-value>
</env-entry>
配置通过环境条目env-entry来配置,配置内容包括:名称、类型和值
资源注射的原理是JNDI,也可以不用注解@Resource,直接查找JNDI获取这三个资源
提交表单信息
实现了一个简单的网页搜索html+servlet+xml,使用了https://duckduckgo.com/html/?q=x网站的搜索引擎,将输出网页截取部分div显示出来。
详情见search.html,SearchServlet.java
search.html的搜索界面,图片是乱找的。。。
搜索结果,可以看见使用了get方法会在地址栏显示出来
由于GET的所有信息都显示在浏览器地址栏里,并可能被记录在缓存里,因此敏感信息(银行卡、密码)和过长内容(>255字符)不能用GET方式
上传文件客户端
需要先修改form表格的enctype属性
Servlet的生命周期
启动:load-on-startup==1(或,第一次请求Servlet:load-on-startup==0)时初始化servlet
init()和destroy()只执行一次,但是service()在每次客户端请求servlet的时候都会被执行
@Override
public void init() throws ServletException {
this.log("running init() method...");
ServletConfig conf = this.getServletConfig(); // 也可以直接this.getInitParameter("startPoint");这里展示ServletConfig()的用法
startPoint = Double.parseDouble(conf.getInitParameter("startPoint"));
}
@Override
public void destroy() {
this.log("running destroy...");
startPoint=0;
}
此外,@PostConstruct和@PreDestroy两个注解都可以使用修饰一个非静态的void()方法,并且它不能有抛出异常声明。
// 两种写法
// 一种写在上面作为注解
@PostConstruct
public void someMethod(){}
//另外一种是写在方法名称内,返回类型的前面
public @PreDestroy void someMethod(){}
注意!!PostConstruct和PreDestroy都是指Servlet这个东西的产生和销毁的之后和之前,
因此PreDestroy是指Servlet的销毁之前,而不是destroy()方法之前执行
* 注解会影响服务器启动速度:服务器启动时会便利Web应用的WEB-INF/classes下所有的class文件与WEB-INF/lib下所有的jar文件,以便检查那些类使用了注解。
** 在<web-app>标签设置metadata-complete="true"可以关闭掉服务器启动时的例行注解检查
Servlet之间的相互跳转
从一个Servlet程序跳转到另外一个,可以很容易地把一项任务分隔开。
Forward
转向是通过RequestDispatcher对象的forward(request, response)来实现的。可以通过request.getRequestDispatcher()方法得到。
// getRequestDispatcher参数必须以“/”开始,表示本Web应用程序的根目录
RequestDispatcher dispatcher = request.getRequestDispatcher("/servlet/LifeCycleServlet");
dispatcher.forward(request,response);
以上代码就能够实现跳转,从当前Servlet跳到LifeCycleServlet去。
Forward能够实现页面跳转,整合业务逻辑
执行forward是在服务器端的改变,具体可以看ForwardServlet.java
重定向Redirect
1** 信息,2** 正确,3** 重定向,4**请求错误,5**服务器错误。301是永久性重定向,302是临时性重定向
Redirect实际上是客户端再次请求,而forward是直接在服务端的页面改变
自动刷新Refresh
自动刷新不仅可以实现一段时间之后自动跳转到另外一个页面,也能实现一段时间之后自动刷新本页面。
反正就是,一段时间之后,刷新访问一个URL路径(可以是别的,也可以是当前的)
url是universal resource locator
uri是universal resource identifier
uri>url,一般uri用于资源,url用于网站
Servlet和线程安全
调用同一个servlet,并发时大家都会占用资源,如果让线程sleep就会发现,新线程的使用的servlet是沉睡中的旧线程
Servlet不是线程安全的,多线程并发的读写会导致数据不同步的问题。解决办法是,尽量不使用name属性,把它分别定义在doGet()或doPost()里面。
虽然使用sychronize(name){}语句块也可以解决问题,但是会造成线程的等待,不是很科学。
小结一下: