首页 > Web开发 > 详细

网站中级

时间:2019-02-28 14:03:47      阅读:144      评论:0      收藏:0      [点我收藏+]
2.1    文件下载
文件上传:将本机文件上传到服务器
文件下载:将服务器上的文件下载到本地
如果是浏览器支持的文件格式,则下载后直接显示
例如:*.html,*.jpg
如果浏览器不支持显示该文件,则提示保存到本地
例如:*.exe,*.rar
注意:都是文件下载,区别是有的能直接显示

不支持下载的目录
Web Application 目录下的文件由tomcat自动支持下载的.
但是:
1.WEB-INF 和META-INF下的文件不支持下载
2.Web Application目录之外的文件不支持下载.

小结:
介绍了什么是文件下载,哪些位置的文件不支持自动下载.
文件下载的本质:tomcat把一个文件的内容传递给客户端.

2.2    自定义下载
自定义下载:使用Servlet自己控制下载
特点:-文件可以在任意位置(Web目录之外)
-可以控制权限
-可以控制下载速度(下载限速)
-可以控制是否允许多点下载(多线程下载)
-其他方面的下载控制

自定义下载的示例:
-所有用户的在头像存储在 c:\data\photo\下    
-头像的命名规则:20180001.jpg
-要求在web项目中显示加载显示头像.

一般可以认为是"/"的作用等同于"\\"
最好用“/”因为java是跨平台的。“\”(在java代码里应该是\\)是windows环境下的路径分隔符,
Linux和Unix下都是用“/”。而在windows下也能识别“/”。所以最好用“/”


自定义下载的实现步骤:
1.添加一个Servlet:DownloadService
2.配置映射路径 /downlad/*
3.从访问URI里取得用户的请求参数,对应到实际的本地文件
4.读取本地文件的内容,发给客户端.

404错误,如果用户访问的目标文件不存在,应返回404错误.


前端无法区别这是一个Servlet还是一个静态文件
如<img src="download/photo/20180001.jpg" />

2.3    内容类型Content-Type
在HTTP应答的头部,需要指定内容的类型
示例:
HTTP/1.1 200
Content-Type:image/jpeg        //内容的类型
Content-Length:51967        //内容的长度
观察2.1的抓包数据,找到Content-Type字段.

内容类型主要分为5大类:
text/*        //文本
image/*        //图片
audio/*        //音频
video/*        //视频
application/*    //应用文件

@WebServlet("/download/*")
public class DownloadService extends HttpServlet
{
    File dataDir = new File("c:/data/");
    
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        // getServletPath() 返回的是 /download ,不含全路径信息
        // String servletPath = request.getServletPath();
        
        // 请求路径
        // requestUri: 例如  /mid0201/download/photo/20180001.jpg
        String requestUri = request.getRequestURI();
        // contextPath: 例如  /mid0201
        String contextPath = request.getContextPath();
        // servletPath: 例如 /download/photo/20180001.jpg
        String servletPath = requestUri.substring( contextPath.length());
        
        // 提取出目标信息 targetPath:  "photo/20180001.jpg"
        String targetPath = servletPath .substring("/download/".length());
        File targetFile = new File(dataDir, targetPath);
        
        // 检查目标文件是否存在
        if(!targetFile.exists() || ! targetFile.isFile())
        {
            System.out.println("目标文件不存在!" +targetFile);
            response.sendError(404);
            return;
        }
        
        // 应答:设置 Content-Type 和 Content-Length 读取目标文件,发送个客户端
        String contentType =  "image/jpeg";//"application/octet-stream"; 
        long contentLength = targetFile.length();
        response.setContentType(contentType);
        response.setHeader("Content-Length", String.valueOf(contentLength));
        
        // 应答:读取目标文件的数据, 发送给客户端
        InputStream inputStream = new FileInputStream(targetFile);
        OutputStream outputStream = response.getOutputStream();
        
        try
        {
            streamCopy (inputStream, outputStream);
        }catch(Exception e)
        {
            try{ inputStream.close();} catch(Exception e2){}
        }
        
        outputStream.close();
    }

    private long streamCopy(InputStream in, OutputStream out) throws Exception
    {
        long count = 0;
        byte[] buf = new byte[8192];
        while (true)
        {
            int n = in.read(buf);
            if (n < 0)
                break;
            if (n == 0)
                continue;
            out.write(buf, 0, n);

            count += n;
        }
        return count;
    }
    
}

测试1
映射:DownloadService->/download/*
比较:
image/jpeg
application/octet-steam
观察浏览器反应.

HTTP应答时,应设置正确的Content-Type
浏览器会结合文件后缀和Content-Type来处理
注:不要求记住,直到它的作用和5种分类即可

2.4    HTTP 404错误
404 Not Found 是一个网站开发里常见的错误
404是啥意思?为什么不能是403,405?
HTTP状态码(HTTP Status Code)
分为4类:
2XX:成功,正常,已受理
3XX:重定向
4XX:请求错误
5XX:服务器错误

常见状态码:
200:ok
206:Partial Content
302:Move temporaily
304:Not Modified
403:ForBidden
404:Not Found

        // 检查目标文件是否存在
Servlet返回应答错误
在Servlet里,可以调用sendError()返回应答错误:

        // 检查目标文件是否存在
        if(!targetFile.exists() || ! targetFile.isFile())
        {
            System.out.println("目标文件不存在!" +targetFile);
            response.sendError(404);
            return;
        }


isFile()
public boolean isFile()测试此抽象路径名表示的文件是否是一个标准文.如果该文件不是一个目录,
并且满足其他与系统有关的标准,那么该文件是标准文件.由Java应用程序创建的所有非目录文件一定是标准文件.
返回:当且仅当此抽象路径名表示的文件存在且是一个标准文件时,返回true;否则返回false; 
抛出:SecurityException,如果存在安全管理器,且其SecurityManager.checkRead(java.lang.String)方法拒绝对文件进行读访问.
exists()
public boolean exists()测试此抽象路径名表示的文件或目录是否存在.
返回:当且仅当此抽象路径名表示的文件或目录存在时,返回true;否则返回false;
抛出:SecurityException如果存在安全管理器,且其SecurityManager.checkRead(java.lang.String)方法拒绝对文件或目录进行写访问.

isFile():判断是否文件,也许可能是文件或者目录
exists():判断是否存在,可能不存在
两个不一样的概念

介绍了HTTP状态码的含义
介绍了用sendError()返回404.

3.1    会话Session
在javaweb开发中,Servlet,Session,Filter是三个最重要的机制.

什么叫会话
和日常生活中的"会话"意思差不多
比如:去拜访一家公司,从敲门到离开的这一过程就是一次会话.
     敲门
xxx-------->Session
   <-------(公司)
     离开

当用户访问网站时,会话指从打开到关闭这一过程
开始:用户打开浏览器,打开网站地址
结束:用户关闭浏览器

请求和会话
从用户开始访问该网站,每一个请求都会关联到后台的一个Session对象.

再Servlet里,可以取得当前的会话对象.
HttpSession session =request.getSession();
每一个会话都有一个ID
String sessionId=session.getId();
演示:在一次会话期间,所有的请求都关联到同一个Session对象.
@WebServlet("/Example1")
public class Example1 extends HttpServlet
{

    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException
    {
        //每一个request都关联到一个session对象上
        HttpSession session=request.getSession();
        //每一个会话都有一个ID
        String sessionId=session.getId();
        System.out.println("访问example1-Session ID: " + sessionId);        
        response.getWriter().append("Served at: ").append(request.getContextPath());
    }

}
访问example1-Session ID: 30A34D43449564344339374868C36FEC
访问example2-Session ID: 30A34D43449564344339374868C36FEC
浏览器未关闭的话是同一个会话.
浏览器关闭后再访问
访问example1-Session ID: 95CF8FA07AC01878D292836AD73FB9DE

思考:
1.如果关闭浏览器,再打开浏览器重新打开网站,后台会创建一个新的会话吗?2.除了Servlet之外,访问静态文件(html,jpg)等请求不是也在会话里吗? 是的
由于静态文件是由tomcat处理,所有没法用代码直接进行演示.

如果有多个用户同时访问网站,那么tomcat会创建多个Session对象,分别表示每一个会话.

引入会话Session概论
1.会话表示从打开网站到离开的一次过程
2.重新打开浏览器,则重启一个新的会话
3.会话期间,该用户所有的请求操作都关联到一个
Session session = request.getSession()

3.2    会话与当前用户
会话的作用:一般用于保存当前用户的信息.
应用演示:
-实现一个提供下载业务的网站
-要求用户必须在登录后才能下载.
演示:下载业务
-添加下载页project.html
-添加DownladService.java 提供下载服务
现在,任何人都可以打开project.html进行下载...
怎样限制用户必须在登录后才能下载呢?

用户登录
-添加登录页面 login.html
-添加登录接口 LoginApi.java
现在,用户输入用户名,密码即可实现登录...
下一步:在DownloadService里如何检查当前用户是否已经登录呢?

现在单独实现了登录页面和下载页面,但是用户可以直接访问登录页面进行下载.
需要在下载页面的session中对用户权限进行审查.在下载页面中检查用户是否登录.

3.3    会话与用户权限.
会话的作用:一般用于保存当前的用户信息.
当用户登录后,把用户的相关信息保存在当前会话里,例如,用户ID,用户权限...

用户登录
-LoginApi.java
当用户登录后,应把用户信息保存到当前会话里
    //在当前会话里保存当前用户的信息
    //在真实项目里,此信息应当从数据库里取得,这里仅供演示
    User user=new User(username,true);
    HttpSession httpSession =this.httpReq.getSession();
    httpSession.setAttribute("user", user);

用户权限检查
-DownloadService.java
在下载之前,检查当前用户的权限
    // 检查当前用户是否已经登录
    User user=(User)request.getSession().getAttribute("user");
    if(user==null)
    {
        System.out.println("用户未登陆,请先登录");
        response.sendRedirect("login.html");    //302重新定向
        return;
    }

download通过前端的超链接        <a href="down?id=20180002"> YY系统源码 </a>
通过识别down调用@WebServlet("/down")注解的方法.

关闭浏览器后再下载,需要重新登录,因为此时后台开启了一个新的会话,user变为null.

3.4    HTTP 302重新定向
重定向,Redirection,即指示客户端打开一个新的位置.
response.sendRedirect("login.html");    //302重定向.

第一步:浏览器发送get请求 down?id=2018001

服务器返回:HTTP/1.1 302 Location:login.html

浏览器发送新的get请求 login.html

服务器返回:200.

4.1     实例-BBS论坛
演示一个BBS论坛系统,从中理解会话的作用
-用户登录 login.html
-浏览      index.html
-发帖      edit.html
代码框架
沿用网站入门篇的af-service框架
-数据库MySQL:af_example.sql
-框架支持
-添加API
-添加前端页面.

4.2     会话的作用.
会话的作用,主要用于保存当前用户的信息

1.在用户登录时,将用户信息保存到当前会话
2.在后续操作时,从会话中取出当前用户信息

1.UserLoginApi登录
在登录时,保存当前用户信息
httpReq.getSession().setAttribute("user",u);

2.ArticleSaveApi登录
在保存发帖时,显然,要知道当前用户是谁
user=(User)httpReq.getSession().getAttribute("user");
row.setCreator(user.id);

更多功能
1.用户注册
2.只看自己的帖子
3.删帖
-管理员删帖
-普通用户删自己的帖子

几乎所有的后台操作,都要检验当前用户的身份和权限.

5.1    权限检查
权限检查:即检查当前用户有没有执行操作的权限
例如,发帖时要检查用户是否登录,有没有发帖权限.

存在的问题,在当前演示系统中,已经有了后台检查,但是缺少前端检查.
比如,如果用户未登陆,那么应该让他根本点不了"发帖界面",而不是编辑好了点保存,才提示说尚未登陆.

添加前端检查
第一种方法:(不推荐)
在点击发帖按钮时,向后台发起请求,检查当前用户的信息.
    User user = (User) httpReq.getSession().getAttribute("user");    //(User)是什么意思    强制类型转换为User类
    if(user == null)
        throw new Exception("请先登录!");

检查权限的两种方式:前端检查和后台检查
后台检查:(必须有)保证系统的安全性,完整性
前端检查:(最好有)增强系统的易用性,避免误操作,提升用户体验


5.2    前端权限检查
存在的问题,在上一节课中,在每次按钮时,向后台发起一个请求SesionInfo.api来获取当前用户信息...
问题:当用户访问量大时,会影响网站效率...

前端权限检查:借助于浏览器缓存,在前端自行完成检查.

一、sessionStroage
sessionStorage是浏览器对象,可以用JavaScript来访问它。前端页面可以把数据存储到这里。
例如,
存数据sessionStorage.setItem (“username”,  “邵发”);
取数据 var name = sessionStorage.getItem(“username”);
注意:
1 当浏览器关闭时,sessionStorage的内容被清空。
2 只能存储字符串。如果想存储一个对象,需要转成字符串。
sessionStorage.setItem (“key”,  JSON.stringify( obj ) );
var obj = JSON.parse( sessionStorage.getItem(“key”));
二、添加前端检查
1 在登录返回时,保存当前用户信息到缓存
(1) 修改 UserLoginApi, 返回当前用户的信息
(2) 修改 login.html
sessionStorage.setItem("user", JSON.stringify(data));

2 在发贴时,检查缓存里的当前用户的信息
修改 index.html 
        // 点击 ‘发贴‘ 按钮
        M.doCreateNew = function()
        {
            var user =JSON.parse(sessionStorage.getItem("user"));
            if(user == null)
            {
                alert("请先登录鸭");
                location.href = "login.html";
            }
            else
            {
                location.href = "edit.html";    
            }        
        }


1.在登录返回时,将当前用户的信息存在缓存里
sessionStorage.setItem("key",value)
2.在点发帖时,从缓存里检查当前用户的信息
value=sessionStorage.getItem("key")

UserLoginApi.java 最后添加

    JSONObject jdata =new JSONObject(u);
    jdata.remove("password");    //不把密码返回给客户端
    return jdata;

小结
认识了浏览器缓存对象 sessionStorage
学会使用sessionStorage存储当前用户信息,实现前端检查,减少服务器的负担.

5.3    后端检查与网络安全
问题:只使用前端检查,不使用后端检查,可以吗
例如:在点"发帖"时,前端检查一次..
在点"发表"时,前端检查一次..
似乎不再需要后端检查了,是不是?

取消后台检查,所有检查都在前端进行.看起来网站功能和以前一样,没有影响..
模拟攻击
不做后台检查的网站,很容易能攻击
演示:
新建一个Java Project,直接调用后台的接口..
显然,攻击者不需要知道用户名和密码,直接可以随意修改网站的数据.

后端检查:必须有,保证安全性,数据完整性.
前端检查:最好有,提升易用性和用户体验.

5.4    显示细节优化.
添加方法

    // 查询,并返回 JSONArray
    public static JSONArray executeQuery2JSON(String sql) throws Exception
    {
        JSONArray result = new JSONArray();
        
        AfSqlConnection connection = getConnection();
        try{
            ResultSet rs = connection.executeQuery(sql); 
            
            // 取得每一列的名称和类型
            ResultSetMetaData rsmd = rs.getMetaData();
            int numColumns = rsmd.getColumnCount(); // 一共几列
            int[] columnTypes = new int[numColumns];  // 每列的类型
            String[] columnLabels = new String[numColumns]; // 每列的标题            
            for(int i=0; i<numColumns; i++) 
            {            
                int columnIndex = i + 1; // 列序号
                columnLabels[i] = rsmd.getColumnLabel(columnIndex); // 列标题    
                columnTypes[i] = rsmd.getColumnType(columnIndex); // 类型, 参考 java.sql.Types定义    
            }
                    
            while(rs.next())
            {        
                // 每一行转成一个JSONObject
                JSONObject jrow = new JSONObject();
                result.put(jrow);
                
                for(int i=0; i<numColumns; i++)
                {
                    String columnValue = rs.getString( i + 1); // 每列的值
                    if(columnValue == null) continue;
                    
                    int type = columnTypes[i];
                    if(type == Types.TINYINT || type == Types.SMALLINT
                        || type == Types.INTEGER || type == Types.BIGINT)
                    {
                        jrow.put(columnLabels[i], Long.valueOf(columnValue));
                    }
                    else if(type == Types.DOUBLE || type == Types.FLOAT)
                    {
                        jrow.put(columnLabels[i], Double.valueOf(columnValue));
                    }
                    else
                    {
                        jrow.put(columnLabels[i], columnValue);
                    }
                }        
            }
            
            return result;
        }finally{
            connection.close();
        }    
    }
使输出为一个JSONArray对象
在前端页面index.html中修改方法M.shouResult
        // 格式化数据并显示
        M.showResult = function(data)
        {
            // 创建一个 AfTemplate对象用于替换
            var templ = new AfTemplate( $(‘.template‘).html());
            
            var target = $(".main .content tbody");
            target.html(""); // 清空
            for(var row of data)
            {    
                row.timeCreated =row.timeCreated.substr(0,16);            
                target.append( templ.replace(row) );
            }
            
            // 如果没有数据,则把下面的提示显示出来
            if(data.length > 0) 
                $(".main .content .empty").hide();
            else 
                $(".main .content .empty").show();
        }

6.1    用户注册
实现用户以手机号进行注册
-数据库af_example.sql
-注册页面 register.html
-登录页面 login.html
手机号需要效验.

6.2    手机短信验证
在用户以手机号进行用户注册时,需要填写验证码
1.绑定手机号
2.点"发送验证码".后台系统将发送一个验证码到这个手机号,同时把验证码存在Session中.
3.用户在手机上查看短信验证码,填写到页面
4.用户点"注册"...后台对检查验证码是否正确..

通过UserSendVerifyApi生成验证码
        // 生成4位验证码
        int randomCode = new Random().nextInt(5000) + 1000;
        String verifyCode = String.valueOf(randomCode);

通过MySmsService发送给手机
public class MySmsService
{
    public static MySmsService i = new MySmsService();
    
    //public static String serviceUrl = "http://127.0.0.1:8080/service/SendSms.api";
    public static String serviceUrl = "http://service.afanihao.cn/SendSms.api";
    
    //http://service.afanihao.cn 上获取 API Key / API Secret
    private String API_KEY = "CAE742E06B27C66D7DD79B3CF3F043BB";
    private String API_SECRET = "C0CDE27BC95F32E4C831A376C8E43F0254F6D55A";
    
    private MySmsService()
    {
        
    }
    
    public void send(String cellphone, String code) throws Exception
    {
        for(int i=0; i<cellphone.length(); i++)
        {
            char ch = cellphone.charAt(i);
            if(ch<‘0‘ || ch >‘9‘)
                throw new Exception("手机号必须全是数字!");
        }
        if(cellphone.length() != 11)
            throw new Exception("手机号必须是11位数字!");
                
        if(code.length() > 6)
            throw new Exception("验证码最大6位!");
        
        JSONObject jreq = new     JSONObject();
        jreq.put("apiKey",    API_KEY.trim());
        jreq.put("apiSecret", API_SECRET.trim());
        jreq.put("target", cellphone);
        jreq.put("code", code);
        
        JSONObject jresp = REST.post(serviceUrl, jreq);
        int error = jresp.getInt("error");
        if(error != 0)
        {
            String reason = jresp.getString("reason");
            throw new Exception(reason);
        }
    }
}

再通过UserRegisterApi验证
        // 检查验证码
        String verifyCode = jreq.getString("verifyCode").trim();    //从前台输入中获取
        String code = (String)httpReq.getSession().getAttribute("verifyCode");    //从会话中的code获取
        if(code == null || ! code.equals( verifyCode))
            throw new Exception("验证码不匹配!");

网站后台如何发送短信
注:参考<Java如何发送手机短信>
本章的例子是可以运行的,在发送短信的环节,需要大家按以下说明修改一下。

1 打开 http://service.afanihao.cn/
2 注册一个账号
3 登录系统,可以看到你的API Key, API Secret
 

4 修改代码 MySmsService.java
 

内部原理:
MySmsService里将手机号、验证码发给给 service.afanihao.cn ,
然后由service.afanihao.cn这台服务器来发送一个短信到目标手机上。

注:如果你有一个已备案的带域名的个人网站,则可以各家云服务提供商自行申请短信功能,
然后用云平台的API来发送短信。(必须是已备案的网站才有可能申请成功)。


7.1    过滤器
Filter
网站开发的三大核心机制:Servlet,Session,Filter

过滤器的作用:对请求进行过滤处理
    filter1 filter2    |--->Servlet
请求---------------------->|
               |--->静态文件

创建过滤器
演示:添加一个Filter
右键new Filter
最主要的是doFilter方法

过滤器的配置
Filter的配置也分注解方式和XML方式
URL规则和Servlet完全一样    //在xml中配置项    <url-pattern>
精准路径:如/home/ex.html
前缀匹配:如/download/*
后缀匹配:如*.api
public void init(FilterConfig fConfig) throws ServletException 初始化方法

public void destroy() 停止时调用.

public void doFilter(ServletRequest request, 
        ServletResponse response, 
        FilterChain chain) throws IOException, ServletException
主要方法.

@WebFilter("/login.html")    //修改注释的拦截路径
注释,访问http://127.0.0.1:8080/demo06/login.html    Filter方法被调用.

不想放行
    public void doFilter(ServletRequest request, 
            ServletResponse response, 
            FilterChain chain)
            throws IOException, ServletException
    {
        System.out.println("Filter 被调用");
        HttpServletResponse httpResp =(HttpServletResponse)response;
        httpResp.sendError(403,"无权访问");
    //    chain.doFilter(request, response);
    }

7.2    访问过滤
假设有一些静态资源放在/download下面,
但是,其中的vip/目录下的资源要求登录后可见.

// servletPath : 例如 /download/vip/yanran.jpg
String servletPath = httpReq.getServletPath();    //.getServletPath()    获取路径

if(servletPath.startsWith("/download/vip/"))    //.startsWith(String prefix)     检测字符串是否以指定的前缀开始。


if(servletPath.startsWith("/download/vip/")
    {        {
    User user = (User) httpReq.getSession().getAttribute("user");
    if(user == null)
        {
        // 第1种:可以返回重定向 302
        // 控制浏览器重定向到 login.html
        // 注意:要计算出绝对路径
        String contextPath = httpReq.getContextPath();
        httpResp.sendRedirect(contextPath + "/login.html");

        // 第2种:可以返回错误 4XX
//        httpResp.sendError(403, "请先登录");

        // 第3种:可以返回200,并返回HTML内容
//        httpResp.setCharacterEncoding("utf-8");
//        httpResp.setContentType("text/html");
//        httpResp.getWriter().write("<html><body>你还没登录呢!</body></html>");
        return;
        }
    }
要点
1.使用前缀匹配/download/*
2.检查会话,确认用户身份
3.在Filter里返回应答
(通过HttpServletResponse对象来返回应答)

进一步理解过滤器写法
通过检查Session信息,让一部分请求通过,另一部分请求直接返回.

7.3    访问过滤(2)
示例:
假设有一些服务接口需要在登录后才能调用
例如:/GetSecret    是一个Servlet服务接口
要求当前用户有相应权限才能调用
              MyFilter3
请求:/GetSecret <----------------->MyService3

 


网站中级

原文:https://www.cnblogs.com/cqbstyx/p/10449286.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!