标签 : Java与Web
HTTP协议
HTTP(hypertext transport protocol),即超文本传输协议.这个协议详细规定了浏览器(Browser)和万维网服务器(WebServer)之间互相通信的规则.其主要特点可简单概括如下:
1) 简单快速: 客户端向服务器请求服务时,只需传送请求方法和路径, 因此使得HTTP服务器的程序规模小,通信速度快; 2) 灵活: HTTP允许传输任意类型的数据对象(传输类型由Content-Type
控制); 3) 无连接: 无连接的含义是限制每次连接只处理一个请求; 4) 无状态: 无状态是指协议对于事务处理没有记忆能力(如果后续处理需要前面的信息,则必须重传.这样可能导致每次连接传送的数据量增大.但如果在服务器不需要先前信息时它的应答就会非常快快).
HTTP请求
一个HTTP请求通常包含三部分(中间已空行隔开):
请求行: (方法 /统一资源标识符URI/协议/版本)请求头: (Accept/Accept-Language等)空行: (CRLF)请求体: (携带的数据信息, GET请求没有)
HTTP请求可以使用HTTP标准中定义的所有请求类型, HTTP1.1支持7种请求类型, 但在互联网应用中最为常用的只有GET与POST.
HTTP-GET
GET /WeChat/cc3200/get_status.do HTTP/1.1Host: aliyunUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3Accept-Encoding: gzip, deflateConnection: keep-alive
- 请求头解析
请求头 | 描述 |
---|---|
User-Agent | 浏览器与操作系统信息 |
Accept | 当前浏览器可以接收的文档类型 |
Accept-Language | 当前浏览器支持的语言 |
Accept-Encoding | 当前浏览器支持的压缩格式:服务器会把数据压缩后再发送到网络中传输 |
Accept-Charset | 当前浏览器支持的编码 |
Connection | 当前浏览器支持的连接方式(keep-alive 即保持一段时间的连接,默认为3000ms) |
Cookie | 如果不是第一次访问该网址,可能会在请求中把上次服务器响应的Cookie数据一并发送过去 |
HTTP-POST
POST /WeChat/cc3200/get_status.do HTTP/1.1Content-Length: 36Cache-Control: max-age=0Origin: http://localhost:8080Content-Type: application/x-www-form-urlencodedReferer: http://localhost:8080/test/...user_name=feiqing&user_password=pass
- 请求头解析
请求头 | 描述 |
---|---|
Referer | 表明请求来自哪个页面 |
Content-Type | application/x-www-form-urlencoded:表单数据类型,说明会使用URL编码来格式化数据 |
Content-Length | 请求体长度 |
user_name=feiqing&user_password=pass | 请求体: 请求携带的数据 |
HTTP响应
一个HTTP响应通常也包含三部分(中间已空行隔开):
响应行: (协议/状态码/描述)响应头: (Server/Content-Length/Set-Cookie等)空行: (CRLF)响应体: (携带的数据)
HTTP响应是由服务器发送给浏览器的数据,浏览器会根据HTTP响应来解析并显示内容:
HTTP/1.1 200 OKServer: Apache-Coyote/1.1Content-Length: 8Date: Sun, 17 Apr 2016 12:39:11 GMT ...
- 响应头解析
响应头 | 描述 |
---|---|
Server | 服务器信息 |
Content-Length | 响应实体长度 |
Set-Cookie | 响应给客户端的Cookie |
Expires: -1; / Cache-Control: no-cache; / Pragma: no-cache; | 设置浏览器不要缓存数据 |
Refresh | 自动刷新页面 |
在HTML文件中可用<meta/>
标签来设置响应头信息:
响应状态码
状态码说明了响应的真正含义:
状态 | 描述 |
---|---|
200 | 请求成功 |
404 | 请求资源没找到 |
500 | 服务器内部错误 |
302 | 重定向: 表示服务器要求浏览器重新再发一个请求到服务器指定的一个Location |
304 | 缓存未过期(服务器资源未曾修改), 详细可参考 |
Tomcat
Tomcat是一个免费开源的Serlvet容器,它是Apache基金会的Jakarta项目中的一个核心项目,由Apache,Sun和其它一些公司及个人共同开发而成. 由于有了Sun的参与和支持, 因此最新的Servlet和Jsp规范总能在Tomcat中得到体现.主页:.
Tomcat目录结构
- bin: 存放可执行脚本文件(如startup.bat/startup.sh等)
- conf: 存放Tomcat相关配置文件:
- server.xml: 整个Tomcat运行环境配置(如端口号/虚拟主机等)
- web.xml: 部署描述符文件(定义了默认JSP/Servlet处理规则,是所有web项目中WEB-INF/web.xml的父文件)
- context.xml: 对所有应用的统一配置.
- lib:Tomcat类库, 该目录中的jar包所有项目共享.
- logs : Tomcat日志目录.
- webapps:存放WEB应用,其每个子目录都是一个项目;
- work:运行时生成的文件.
server.xml
- 元素解析
元素 | 描述 |
---|---|
<Server/> | 根元素,整个Tomcat的配置信息 |
<Service/> | 服务(在<Server/> 中只能有一个<Service/> ) |
<Connector/> | 连接 |
<Engine/> | 引擎,是<Service/> 组件核心 |
<Host/> | 每个<Host/> 元素表示一台虚拟主机.每台虚拟主机都有自己的主机名和项目目录 |
<Context/> | 每个<Context/> 元素表示一个应用.如果应用在<Host/> 的appBase指定的目录下,那么可以不配置<Context/> 元素,如果是外部应用,那么就必须配置<Context/> |
Tomcat配置
1. 配置端口号
编辑%CATALANA_HOME%\conf\server.xml文件中的<Connector/>
元素
2. 配置外部应用
配置外部应用之后, 项目就可以不用拷贝到webapps目录下,自定义项目存放位置,其配置方式有两种:
- 1: 修改server.xml 在
<Host/>
元素中添加<Context/>
元素
如果指定path为空(path=”“), 则默认访问的项目就是/home/www/test, 而不再是webapps下的ROOT.
- 2: 编辑conf/catalana/localhost目录: 新增test.xml文件
存放到%CATALANA_HOME%/conf/catalana/localhost目录下, 文件名即为应用名.
Servlet
Servlet技术核心就是Servlet
接口,所有Servlet实现类都必须实现Servlet
接口,Servlet容器(如Tomcat)会把Servlet类加载到内存并生成唯一实例,当有请求到来时调用其方法.
- 实现Servlet方式有三种:
- 实现
javax.servlet.Servlet
接口 - 继承
javax.servlet.GenericServlet
类 - 继承
javax.servlet.http.HttpServlet
类
- 实现
Servlet
Servlet
接口定义如下
public interface Servlet { public void init(ServletConfig config) throws ServletException; public ServletConfig getServletConfig(); public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; public String getServletInfo(); public void destroy();}
方法 | 描述 |
---|---|
init | 在第一次请求该Servlet(默认)或容器启动时, Servlet容器就会调用init() , 且只调用一次 |
service | 每次请求Servlet都会调用该方法 |
destroy | 销毁Servlet时(卸载应用/关闭容器时), 调用该方法 |
- HelloServlet
/** * @author jifang. * @since 2016/4/17 8:32. */public class HelloServlet implements Servlet { private ServletConfig config; public void init(ServletConfig config) throws ServletException { System.out.println("init()..."); this.config = config; System.out.println("config: <" + config + ">"); } public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { System.out.println("service()..."); System.out.println("req: <" + req + ">, res: <" + res + ">"); res.getWriter().print("HelloServlet
"); } public void destroy() { System.out.println("destroy()..."); } public ServletConfig getServletConfig() { return this.config; } public String getServletInfo() { return null; }}
- web.xml
JavaWeb HelloServlet com.fq.web.servlet.HelloServlet HelloServlet /hello_servlet.do
url-pattern
<url-pattern/>
用来指定Servlet的访问路径,必须以/
开头.
- 可以在
<servlet-mapping/>
配置多个<url-pattern/>
, 此时一个Servlet实例就绑定多个URL.- 可以在
<url-pattern/>
中使用通配符*
,可以使一个Servlet绑定一组URL, 但*
不能出现在中间位置,也不能只有*
通配符, 另外, 通配符只是一种模糊匹配URL的方式,如果存在更具体的<url-pattern/>
,那么会优先选择精确匹配.配置在容器启动时创建Servlet实例
HelloServlet com.fq.web.servlet.HelloServlet 1
<load-on-startup/>
元素可以让容器在启动时就创建该Servlet实例(调用init()
方法),注意<load-on-startup/>
元素的值必须是>=0
的整数,它代表容器启动时创建Servlet实例的顺序.
GenericService
GenericService
抽象类实现了Servlet
接口并完成以下工作:
init()
方法中的ServletConfig
赋给一个实例变量, 使他可以通过getServletConfig()
来获取. 2. 为Servlet接口的所有方法提供默认实现. 3. 提供方法来包装ServletConfig
. - Generic部分代码
public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable{ private transient ServletConfig config; public GenericServlet() { } public void destroy() { } public String getInitParameter(String name) { ServletConfig sc = getServletConfig(); if (sc == null) { throw new IllegalStateException( lStrings.getString("err.servlet_config_not_initialized")); } return sc.getInitParameter(name); } public EnumerationgetInitParameterNames() { ServletConfig sc = getServletConfig(); if (sc == null) { throw new IllegalStateException( lStrings.getString("err.servlet_config_not_initialized")); } return sc.getInitParameterNames(); } public ServletConfig getServletConfig() { return config; } public ServletContext getServletContext() { ServletConfig sc = getServletConfig(); if (sc == null) { throw new IllegalStateException( lStrings.getString("err.servlet_config_not_initialized")); } return sc.getServletContext(); } public String getServletInfo() { return ""; } public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); } public void init() throws ServletException { } public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; public String getServletName() { ServletConfig sc = getServletConfig(); if (sc == null) { throw new IllegalStateException( lStrings.getString("err.servlet_config_not_initialized")); } return sc.getServletName(); }}
HttpServlet
HttpServlet
是GenericServlet
的子类,它提供了对HTTP协议的支持.覆盖了GenericServlet
的service()
方法,并新增了接受HttpServletRequest
/HttpServletResponse
参数的service()
方法:
@Overridepublic void service(ServletRequest req, ServletResponse res) throws ServletException, IOException{ HttpServletRequest request; HttpServletResponse response; if (!(req instanceof HttpServletRequest && res instanceof HttpServletResponse)) { throw new ServletException("non-HTTP request or response"); } request = (HttpServletRequest) req; response = (HttpServletResponse) res; service(request, response);}protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{ String method = req.getMethod(); if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); if (ifModifiedSince < lastModified) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if (method.equals(METHOD_POST)) { doPost(req, resp); } else if (method.equals(METHOD_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { doTrace(req,resp); } else { // // Note that this means NO servlet supports whatever // method was requested, anywhere on this server. // String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); }}
- 原始的
service()
将请求/响应向下转型为HttpServletRequest
/HttpServletResponse
, 并调用新的service()
. 由于HttpServlet
在新的service()
方法中已经做了很多工作, 因此在继承HttpServlet
实现自动以Servlet时, 则只需覆盖doGet()
/doPost()
等即可, 而没有必要覆盖service()
(极少数情况需要覆盖doHead()
等)
注意: Request/Response向下转型总会成功:因为在调用
service()
方法时,Servlet容器总会预计使用HTTP,从而直接创建并传递HttpServletRequest
/HttpServletResponse
实例.
- HelloHttpServlet
/** * @author jifang. * @since 2016/4/20 19:48. */@WebServlet(name = "HelloHttpServlet", urlPatterns = "/hello_http_servlet.do")public class HelloHttpServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("doPost() ..."); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("doGet() ..."); }}
HttpServletRequest
对于每一个HTTP请求, Servlet容器会在调用service()
方法时创建Request实例并传递给service
形参, HttpServletRequest
是Request在HTTP环境下的实例,其封装了有关请求的信息:
- 封装请求头信息;
- 封装请求正文数据(GET没有正文);
- 提供请求转发/包含功能;
- 作为域对象, 可以传递数据.
获取请求头
方法 | 描述 |
---|---|
String getHeader(String name) | Returns the value of the specified request header as a String. |
Enumeration<String> getHeaderNames() | Returns an enumeration of all the header names this request contains. |
long getDateHeader(String name) | Returns the value of the specified request header as a long value that represents a Date object. |
Enumeration<String> getHeaders(String name) | Returns all the values of the specified request header as an Enumeration of String objects. |
int getIntHeader(String name) | Returns the value of the specified request header as an int. |
String getRemoteAddr() | Returns the Internet Protocol (IP) address of the client or last proxy that sent the request. |
String getMethod() | Returns the name of the HTTP method with which this request was made, for example, GET, POST, or PUT. |
String getContextPath() | Returns the portion of the request URI that indicates the context of the request. |
- 获取请求来源
@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String referer = request.getHeader("Referer"); String userAgent = request.getHeader("User-Agent"); composeResponse(referer, userAgent, response);}private void composeResponse(String referer, String userAgent, HttpServletResponse response) throws IOException { response.setHeader("Content-Type", "text/html;charset=utf-8"); PrintWriter writer = response.getWriter(); if (!Strings.isNullOrEmpty(referer)) { writer.print("来源地址: " + referer + "
"); } else { writer.print("来自浏览器地址栏
"); } writer.print("
"); writer.print("来源信息: " + userAgent + "
");}
获取请求参数
方法 | 描述 |
---|---|
String getParameter(String name) | Returns the value of a request parameter as a String, or null if the parameter does not exist. |
Map<String,String[]> getParameterMap() | Returns a java.util.Map of the parameters of this request. |
Enumeration<String> getParameterNames() | Returns an Enumeration of String objects containing the names of the parameters contained in this request. |
String[] getParameterValues(String name) | Returns an array of String objects containing all of the values the given request parameter has, or null if the parameter does not exist. |
- 获取微信请求消息
@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Element root; String xml = request.getParameter("xml"); try { if (!Strings.isNullOrEmpty(xml)) { root = new SAXReader().read(new StringReader(xml)).getRootElement(); } else { String data = CharStreams.toString(new InputStreamReader(request.getInputStream())); root = new SAXReader().read(new StringReader(data)).getRootElement(); } } catch (DocumentException | IOException e) { LOGGER.error("parse wx xml error", e); throw new RuntimeException(); } // ...}
请求转发/包含
Request提供了getRequestDispatcher()
来获取一个RequestDispatcher
, 并由其提供请求转发/请求包含功能.
Request方法 | 描述 |
---|---|
RequestDispatcher getRequestDispatcher(String path) | Returns a RequestDispatcher object that acts as a wrapper for the resource located at the given path. |
请求转发/请求包含都是由多个Servlet协作完成一个请求, 因此需要从一个Servlet中跳到另一个Servlet中:
RequestDispatcher方法 | 描述 |
---|---|
void include(ServletRequest request, ServletResponse response) | Includes the content of a resource (servlet, JSP page, HTML file) in the response. |
void forward(ServletRequest request, ServletResponse response) | Forwards a request from a servlet to another resource (servlet, JSP file, or HTML file) on the server. |
- 请求转发: 原Servlet只会保留设置的响应头信息.
- 请求包含: 原Servlet既会保留响应头, 还会保留响应体内容.
注意: 请求转发时, 可能会因为原Servlet设置了过多的响应体内容导致抛出异常
java.lang.IllegalStateException: Cannot forward after response has been committed
域对象传递数据
由于请求转发/请求包含都只是一次请求, 因此在多个Servlet之间都是共用一个Reqeust, 因此可以利用Request的在多个Servlet之间共享数据:
方法 | 描述 |
---|---|
Object getAttribute(String name) | Returns the value of the named attribute as an Object, or null if no attribute of the given name exists. |
Enumeration<String> getAttributeNames() | Returns an Enumeration containing the names of the attributes available to this request. |
void setAttribute(String name, Object o) | Stores an attribute in this request. |
void removeAttribute(String name) | Removes an attribute from this request. |
HttpServletResponse
同Request, Servlet容器会在每次调用service()
方法时创建Response实例并传递给service()
形参, HttpServletResponse
是Response绑定在HTTP环境下的实例, 其隐藏了将响应发送给浏览器的复杂性:
- 设置响应状态码;
- 设置响应头信息;
- 设置响应正文;
设置响应状态码
方法 | 描述 |
---|---|
void setStatus(int sc) | Sets the status code for this response. |
void sendError(int sc) | Sends an error response to the client using the specified status code and clears the buffer. |
void sendError(int sc, String msg) | Sends an error response to the client using the specified status and clears the buffer. |
关于状态码的描述, 详见HTTP协议部分介绍, 在此就不再赘述.
- 响应404
@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // response.sendError(404, "nothing!!"); response.setStatus(404);}
设置响应头信息
方法 | 描述 |
---|---|
void setHeader(String name, String value) | Sets a response header with the given name and value. |
void addHeader(String name, String value) | Adds a response header with the given name and value. |
void setIntHeader(String name, int value) | Sets a response header with the given name and integer value. |
void addIntHeader(String name, int value) | Adds a response header with the given name and integer value. |
void addDateHeader(String name, long date) | Adds a response header with the given name and date-value. |
void setDateHeader(String name, long date) | Sets a response header with the given name and date-value. |
void sendRedirect(String location) | Sends a temporary redirect response to the client using the specified redirect location URL and clears the buffer. |
关于HTTP响应头的描述, 详见HTTP协议部分介绍, 在此就不再赘述.
- 设置禁用浏览器缓存(Cache-Control, pragma, expires)
@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setHeader("Cache-Control", "no-cache"); response.setHeader("pragma", "no-cache"); response.setDateHeader("expires", -1);}
- 设置重定向(302, Location)
@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); response.setHeader("Location", "http://www.baidu.com");}
HttpServletResponse还提供了另外一种重定向的方式, 直接使用
sendRedirect()
方法, 避免了以上的步骤:
@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.sendRedirect("http://www.baidu.com");}
设置响应正文
Response提供了如下两个方法来获取输出流对象以响应HTTP正文内容
方法 | 描述 |
---|---|
ServletOutputStream getOutputStream() | Returns a ServletOutputStream suitable for writing binary data in the response. |
PrintWriter getWriter() | Returns a PrintWriter object that can send character text to the client. |
OutputStream传输二进制数据流(字节数据), 常用作文件下载; Writer传输字符数据, 常用作响应HTTP正文内容(如HTML/XML等).
注意: 在一个请求中,不能同时使用这两个流, 否则会抛出
IllegalStateException
.
字符响应流
@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter writer = response.getWriter(); writer.print(""); writer.print("content
"); writer.print("");}
- 缓冲区
PrintWriter
的默认缓冲区大小为8K, 因此当响应数据大小<8K时, 数据存放在缓冲区, 而不会立刻发送到浏览器, 直到Servlet执行结束,因此如果希望马上发送给浏览器, 需要调用Response的flushBuffer()
方法手动刷新缓冲区.
ServletConfig
在容器初始化Servlet时, 会将一个ServletConfig
实例传给init()
方法,其封装了@WebServlet
/部署描述符传递给Servlet的配置信息:
方法 | 描述 |
---|---|
String getInitParameter(String name) | Gets the value of the initialization parameter with the given name. |
Enumeration<String> getInitParameterNames() | Returns the names of the servlet’s initialization parameters as an Enumeration of String objects. |
ServletContext getServletContext() | Returns a reference to the ServletContext in which the caller is executing. |
- java
public void init(ServletConfig config) throws ServletException { this.config = config; Enumerationnames = config.getInitParameterNames(); while (names.hasMoreElements()) { String name = names.nextElement(); String value = config.getInitParameter(name); System.out.println(name + " -> " + value); }}
- web.xml
HelloServlet com.fq.web.servlet.HelloServlet admin com.fq zhujifang666@163.com
ServletContext
ServletConfig
中提供了获取ServletContext
的方法getServletContext()
, ServletContext
代表Servlet应用程序,且每个应用程序仅有一个ServletContext
实例,其在容器启动时创建, 在容器关闭时销毁, 因此可以利用其在多个Servlet中传递数据.
方法 | 描述 |
---|---|
void setAttribute(String name, Object object) | Binds an object to a given attribute name in this ServletContext. |
Object getAttribute(String name) | Returns the servlet container attribute with the given name, or null if there is no attribute by that name. |
Enumeration<String> getAttributeNames() | Returns an Enumeration containing the attribute names available within this ServletContext. |
void removeAttribute(String name) | Removes the attribute with the given name from this ServletContext. |
应用初始化参数
前面看到ServletConfig
可以获取针对本Servlet的初始化参数,而利用ServletContext
可以获取针对本应用程序的公共初始化参数:
方法 | 描述 |
---|---|
String getInitParameter(String name) | Returns a String containing the value of the named context-wide initialization parameter, or null if the parameter does not exist. |
Enumeration<String> getInitParameterNames() | Returns the names of the context’s initialization parameters as an Enumeration of String objects, or an empty Enumeration if the context has no initialization parameters. |
- web.xml
admin feiqing zhujifang666@163.com
- java
@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletContext context = getServletContext(); String admin = context.getInitParameter("admin"); String email = context.getInitParameter("e-mail"); System.out.printf("admin: %s%n", admin); System.out.printf("e-mail: %s%n", email);}
获取资源
可以使用ServletContext
来获取Web应用下的资源路径/资源流等内容:
方法 | 描述 |
---|---|
String getRealPath(String path) | Gets the real path corresponding to the given virtual path. |
URL getResource(String path) | Returns a URL to the resource that is mapped to the given path. |
InputStream getResourceAsStream(String path)` | Returns the resource located at the named path as an InputStream object. |
Set<String> getResourcePaths(String path) | Returns a directory-like listing of all the paths to resources within the web application whose longest sub-path matches the supplied path argument. |