1.应用程序和用户界面
互联网用户和数据库往往不会直接打交道,而是通过应用程序对数据库进行间接访问。
在计算机发展早期,应用程序在大型主计算机上运行,用户通过终端与应用程序交互。
个人计算机的发展导致了带有图形的用户界面GUI的数据库应用的发展。程序在个人计算机上运行,这些代码直接与一个共享的数据库进行通信。这种模式被称为客户-服务器体系结构。
这种模式至少有两个问题:
用户机器可以直接访问数据库,从而带来安全性问题。
维护困难。对应用程序或数据库的任何更改(扩展、更新、修改等)都要求位于客户计算机上的应用程序的所有副本一起更改(重新部署软件)。
现在有两种方法用于避免上述问题。
-browser/server。web浏览器提供前端,通过前端访问后端。这样就不需要单独在客户机安装、维护软件。同时,与c语言编写的程序不同,前端的脚本语言JavaScript可以运行在安全模式下,保证不会导致安全问题。
应用程序安装在独立设备上。这些设备主要是移动设备,它们通过API与后端应用程序进行通信,并不能直接访问数据库。
2.Web基础
2.1 同一资源定位符
统一资源定位符(Uniform Resource Locator)是web上可以访问的每份文档的全球唯一名称。比如:
http://www.acm.org/sigmod
上面的URL由三部分组成,http是超文本传输协议,“https”是“http”的安全版本,并且是当今的首选模式。第二部分是一台具有web服务器的机器名称。第三部分是该机器上文档的路径或者唯一标识。
URL还可以包含位于web服务器上程序的标识,以及传递给该程序的参数。并由该程序返回一个html文档给web服务器。
http://www.google.com/search?q=silberschatz
2.2 超文本标记语言
下图就是一个html创建一个表单的过程
Http定义了两种请求方式,上图所示的get请求将请求参数作为URL的一部分,另外一种请求Post请求则发送一个请求,将参数值作为web服务器和浏览器之间交换的HTTP协议的一部分发送。
有些编辑器支持使用图形界面来直接创建HTML文本编辑器。
CSS支持给html提供样式。
HTML5支持多种形式的输入类型,比如日期和时间选择,文件选择,还支持对输入采取限制(最大值,最小值等)
2.3 web服务器和会话
Web服务器是运行于服务机上的程序,它接收浏览器的请求,根据其提供的参数执行程序,最后将结果以HTML文档的形式将结果传送回去。
下图显示了一个使用三层体系结构搭建的web应用程序。通用网关接口(CGI)标准定义了web服务器如何与应用程序进行通信。使用多层服务器增加了系统的开销,CGI接口为每个请求都启动一个新的进程为之服务,这导致了更大的开销。
因此目前大部分的应用程序将web服务器和应用服务器合二为一,采用两层web应用程序体系结构。
用户通过JDBC或者ODBC来访问数据库时,则会建立一个会话,会话信息会一直保存,直到该会话终止。但是客户端和web服务器之间不存在长连接,往往是连接-请求-响应-关闭连接的方式,这是为了更多的容载海量的用户访问,降低连接限制带来的影响。
尽管连接会关闭,但是web应用程序也需要会话信息来允许有意义的用户交互,对用户进行认证等。它的策略是,会对每一次会话进行一次用户认证,会话的进一步交互无需进行认证。
为了实现会话,需要在客户端存储额外的信息。这些额外的信息通常以cookie的形式维护在客户端,一个cookie是一段包含标识信息的文本,并且与一个名称相关联。例如,google.com可能设置一个名为prefs的cookie,它对用户的偏好设置进行编码,比如语言偏好和每页显示的结果数目,对于每个搜索请求,google.com都能够从用户的浏览器得到这个名为prefs的cookie,然后根据其指定的偏好来显示结果。一个域(Web站点)只允许获取它自己设置的cookie,而不能获取其他域所设置的cookie,而且cookie的名称可以跨域重用。
为了跟踪用户会话,服务端会生产一个名为seesionid的cookie,这是一个特殊的cookie,它用于区分不同的会话,因此也会在服务端存储。当一个请求进来时,应用服务器从客户端请求名为seesionid的cookie,如果客户端没有存储该cookie,或者返回的值与服务端存储的有效会话标识不同,就认为该请求不是当前会话的一部分。
对于安全性要求不高的应用,比如公共新闻站点,cookie可以永久的存储在浏览器端和服务器段。他们识别初用户对一个站点的后续访问,而不需要输入任何验证信息。
对于安全性高的应用,则可能会设置时间限制,在超时后或者用户注销(退出登录)时使会话失效,使会话失效其实就是将会话标识从服务端删除。
3.Servlet
java servlet(Java服务端程序)规范定义了一种用于在WEB/应用服务器与应用程序之间进行通信的应用编程接口。Java的HttpServlet类实现了Servlet API的规范。
3.1 Servlet示例
我们卡妈妈提到了如下的一个表单请求。
现在假设该请求被提交给后端,我们编写下对应的后端处理逻辑代码,看看后端要怎么应对前端的请求
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; @WebServlet("PersonQuery") public class PersonQueryServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResonse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = resonse.getWriter(); ...检查用户是否已登录... out.println("<HEAD><TITLE>Query Result</TITLE></HEAD>"); out.println("<BODY>"); String persontype = request.getParameter("persontype"); String name = request.getParameter("name"); if(persontype.equals("student")) { ...寻找具有指定姓名的学生的代码... ...使用JDBC与数据库进行通信... ...假设已获取到ResultSet rs,并且... ...包含属性:ID、姓名与系名... String headers = new String[]{"ID","Name","Department Name"}; Util::resultSetToHtml(rs,headers,out); } else { ...同上,但是对于教师的... } out.println("</Body>"); out.close(); } }
我们终于知道服务器对HTTP请求的原理了,当服务器接收到请求来执行一个特定的servlet时,servlet的代码被加载到Web/应用服务器中,servlet的任务就是处理这种请求,访问数据库以检索出必要的信息,并动态生成一个HTML页面返回给客户端浏览器。
我们注意到,前端指定了ation = "PersonQuery",而后端则使用注解@WebServlet("PersonQuery")来显示的注释当前servlet是用来处理对PersonQuery的请求的。而且前端的表单指定使用HTTP的Get机制,因此servlet的doGet()方法将会被执行。
每次servlet请求都导致在执行调用的内部生成一个新的线程,因此多个请求就可以被并行处理。
请求将cookie和参数放入一个HttpServletRequest对象 中,后端通过api对其进行提取,根据提取参数执行数据库的查询工作,最后将其通过HttpServletResonse 对象返回。
结果是这样输出给response的,我们通过它获取了一个PrintWriter 对象,将要返回的html通过该对象输出,其中查询到的数据输出的方式是Util.resultSetToHtml()实现的。其参考代码如下。
3.2 Sevlet会话
cookie可以用来识别一个请求与前一个请求是否来自同一个浏览器会话。其在后端servlet处理的逻辑是怎么样的呢?
servlet的API中提供了跟踪会话技术的方法。调用HttpServletRequest中的getSession(false)可以获取来自浏览器的HttpSession对象。当该方法被调用是,将会首先要求哦i客户端返回一个具有指定名称的cookie,如果没有该cookie,则说明该请求不是正在进行的会话的一部分。
此时getSession会返回一个空值,引导用户到登入页面。登录页面允许用户提供用户名和密码,登录页面所对象的servlet会验证用户的信息。
如果用户通过认证,登录servlet会话会执行getSession(true),这个方法会创建一个新的会话。为了创建一个新的会话,服务器内部会执行如下任务:在客户端浏览器中设置一个cookie(比如名为sessionId),该cookie用会话标识作为它所关联的值。创建一个新的会话对象,并将会话标识的值与该会话对象相关联。
servlet代码还能够在HttpSession对象中存储和查找(属性,值)对,以便在一个会话内的多个请求之间维持状态。比如,用户通过登录认证并且会话对象被创建之后,可以将userid存储为会话的一个参数,标记该用户已经通过了登录认证:
session.setAttribute("userid",userid)
那么我们只需要取出userid,就可以判断会话用户有没有通过登录认证。如果没有,就可以通过下面的方式让其回到登录页。
为了详细说明上面的内容,我们看如下登录案例(转载)。
需要的页面:
login.jsp:登录页面,提供登录表单;
index1.jsp:主页,显示当前用户名称,如果没有登录,显示您还没登录;
index2.jsp:主页,显示当前用户名称,如果没有登录,显示您还没登录;
Servlet:
LoginServlet:在login.jsp页面提交表单时,请求本Servlet。在本Servlet中获取用户名、密码进行校验,如果用户名、密码错误,显示“用户名或密码错误”,如果正确保存用户名session中,然后重定向到index1.jsp;
当用户没有登录时访问index1.jsp或index2.jsp,显示“您还没有登录”。如果用户在login.jsp登录成功后到达index1.jsp页面会显示当前用户名,而且不用再次登录去访问index2.jsp也会显示用户名。因为多次请求在一个会话范围,index1.jsp和index2.jsp都会到session中获取用户名,session对象在一个会话中是相同的,所以都可以获取到用户名!
login.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>login.jsp</title> </head> <body> <h1>login.jsp</h1> <hr/> <form action="/day06_4/LoginServlet" method="post"> 用户名:<input type="text" name="username" /><br/> <input type="submit" value="Submit"/> </form> </body> </html>
index1.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>index1.jsp</title> </head> <body> <h1>index1.jsp</h1> <% String username = (String)session.getAttribute("username"); if(username == null) { out.print("您还没有登录!"); } else { out.print("用户名:" + username); } %> <hr/> <a href="/day06_4/index2.jsp">index2</a> </body> </html>
index2.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>index2.jsp</title> </head> <body> <h1>index2.jsp</h1> <% String username = (String)session.getAttribute("username"); if(username == null) { out.print("您还没有登录!"); } else { out.print("用户名:" + username); } %> <hr/> <a href="/day06_4/index1.jsp">index1</a> </body> </html>
LoginServlet
public class LoginServlet extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=utf-8"); String username = request.getParameter("username"); if(username.equalsIgnoreCase("cloud")) { response.getWriter().print("用户名或密码错误!"); } else { HttpSession session = request.getSession(); session.setAttribute("username", username); response.sendRedirect("/day06_4/index1.jsp"); } } }
3.3 Servlet的生命周期
servlet的生命周期由部署它的web/应用服务器来控制,当由客户端请求一个特定的servlet时,服务器首先检查是否存在该servlet的一个实例。如果不存在,web服务器就将servlet类加载进java虚拟机,并创建一个servlet类的实例。另外,服务器调用init()方法来初始化该servlet实例。每个servlet实例仅在它被加载的时候被初始化一次。
在确定servlet实例存在后,服务器调用servlet的service方法,并以一个request对象和一个response对象作为参数,在缺省的情况下,服务器创建一个新的线程执行service方法,因此,一个servlet上的多个请求就可以并行执行,而不必等待之前的线程执行完。service方法视情况调用doGet或doPost.
当不再需要的时候,可以通过调用destroy()方法来关闭一个servlet。服务器可以设置一个超时时限,如果在超时时限内没有对一个servlet进行过一个请求,则自动关闭该servlet。超时实现是一个参数,可以根据应用来适当的对它进行设置。
3.4 应用服务器
最知名的servlet应用服务器是Apache的Tomcat。
开发Servlet的应用程序的最佳方式是使用Idea,eclipse等Ide编辑器,他们内置有Tomcat服务器。
除了最基本的servlet支持之外,应用服务器通常还提供了各种有用的服务,它们允许应用程序被部署或者被停止,并且它们提供了监控应用服务器的功能,包括性能监控。还支持跨多个应用服务器的并行处理,处理对象等。