NiceLeeのBlog 用爱发电 bilibili~

Java Http文件服务器Demo

2019-02-12
nIceLee

阅读:


有些东西学起来当时嗯嗯哦就过去了,听着还像是那回事儿,人问起来也能答出个五六七八,但真要理解,还真得自己上手摆弄一遍。
不把原理弄清楚了,哪怕知道怎么个操作法儿,实际上心里还是特别虚。把基础弄明白了,哪怕发展再快,万变不离其宗,还能蹦跶出什么来。
再说个题外话,《计算机网络》这门课这是门神课,可惜可惜。。。

写在前面

写在中间

常见请求/回复示例

普通Get请求示例:

GET / HTTP/1.1
Host: 127.0.0.1:7778
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: zh-CN,zh;q=0.8

//注意Header结束后Body之前(如果有的话)有一个\r\n

普通Response 200OK示例:

...
String html = "<html><head><title>test</title></head><body><h1>测试</h1></body></html>";
writer.write("HTTP/1.1 200 OK\r\n");
writer.write("Date: "+ HttpResource.GMTDateFormat.format(System.currentTimeMillis()));
writer.write("\r\nContent-Type: text/html; charset=UTF-8\r\n");
writer.write("Content-Length: "+ html.length()+ "\r\n");
writer.write("\r\n");
//上面Header结束,以下是内容,Content-Length很关键
writer.write(html);
writer.flush();

授权认证示例

客户端访问服务器,服务器关注CookiesAuthorization标签,

  • Cookies通过,正常处理;
  • Authorization通过,返回Set-Cookie新建会话时长有效期的Session cookie,其它正常处理;
  • 未通过认证,返回WWW-Authenticate头域告知需要鉴权

未通过回复示例:

httpResponse.dataLength = HttpResource.PAGE_401.length;
httpResponse.headers.put("WWW-Authenticate", "Basic realm=\"NiceLee's Site\"");
headerTrans.transferCommonHeader(httpResponse, out);

// out date-length & data
out.write("Content-Length: ".getBytes());
out.write(("" + httpResponse.dataLength).getBytes());
out.write(HttpResource.BREAK_LINE);
out.write(HttpResource.BREAK_LINE);
out.write(HttpResource.PAGE_401);

授权Get请求示例:

GET /sources HTTP/1.1
Host: 127.0.0.1:7777
Connection: keep-alive
Authorization: Basic YWRtaW46YWRtaW4=
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: zh-CN,zh;q=0.8

//注意Header结束后Body之前(如果有的话)有一个\r\n

断点续传示例

断点续传Get请求示例,重点在Range

GET /sources/test.mp4 HTTP/1.1
Accept:*/*
Accept-Encoding:identity;q=1, *;q=0
Accept-Language:zh-CN,zh;q=0.8
Authorization:Basic YWRtaW46YWRtaW4=
Cache-Control:no-cache
Connection:keep-alive
Cookie:Hm_lvt_5e8b566b55a65225efb0997910ade81f=1547108968,1547616967; Hm_lvt_11f780e4e4ccd4e99b101eac776e93e4=1548511001,1548560945,1548578471,1550039650; Hm_lpvt_11f780e4e4ccd4e99b101eac776e93e4=1550047568
Host:127.0.0.1:7777
Pragma:no-cache
Range:bytes=225935360-
Referer:http://127.0.0.1:7777/sources/test.mp4
User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0

断点续传回复示例。若不从头开始,返回206;注意Content-Range

HTTP/1.1 206 Partial Content
Accept-Ranges:bytes
Connection:keep-alive
Content-Length:1729656
Content-Range:bytes 225935360-227665015/227665016
Content-Type:video/mp4
Date:Wed, 13 Feb 2019 16:54:15 GMT
Last-Modified:Sat, 19 Jan 2019 11:42:33 GMT

部分代码:

String range;
if ((range = httpRequest.headers.get("range")) != null) {
    // System.out.println("Range Required: " +range);
    Pattern patternFileRange = Pattern.compile("^bytes=([0-9]+)-([0-9]*)$");
    Matcher matcher = patternFileRange.matcher(range);
    if (matcher.find()) {
        long begin = Long.parseLong(matcher.group(1));
        long end = file.length() - 1;
        try {
            end = Long.parseLong(matcher.group(2));
            end = end < (file.length() - 1) ? end : (file.length() - 1);
        } catch (Exception e) {
        }
        if (begin > 0) {
            httpResponse.do206();
        }
        headerTrans.transferCommonHeader(httpResponse, out);
        dataTrans.transferFileWithRange(begin, end, out, file);
    } else {
        headerTrans.transferCommonHeader(httpResponse, out);
        dataTrans.transferFileCommon(out, file);
    }
} 

缓存文件校对

比较简单,直接上码:

String cacheTime = httpRequest.headers.get("If-Modified-Since");
//System.out.println("headers 缓存时间: " + cacheTime);
if (cacheTime != null) {
    try {
        //System.out.println("对方本地浏览器缓存时间" + HttpResource.GMTDateFormat.parse(cacheTime).getTime() );
        //System.out.println("服务器文件时间" + file.lastModified() );
        if (HttpResource.GMTDateFormat.parse(cacheTime).getTime() + 1000 >= file.lastModified()) {
            doResponseWithFileNoChange(httpResponse, out);
            //System.out.println("对方本地浏览器已有缓存, 返回304");
            return;
        }
        //System.out.println("对方本地浏览器已有缓存, 但已过时");
    } catch (Exception e) {
        e.printStackTrace();
    }
}

返回304方法:

/**
 * 若URL对应的文件缓存未过时, 使用该方法返回
 * 
 * @param httpResponse
 * @param writer
 * @throws IOException
 */
public static void doResponseWithFileNoChange(HttpResponse httpResponse, BufferedOutputStream out)
        throws IOException {
    HttpHeaderTransfer headerTrans = new HttpHeaderTransfer();

    // 304
    httpResponse.do304();
    httpResponse.dataLength = 0;
    headerTrans.transferCommonHeader(httpResponse, out);

    // out date-length & data
    out.write("Content-Length: 0".getBytes());
    out.write(HttpResource.BREAK_LINE);
    out.write(HttpResource.BREAK_LINE);
    out.flush();
}

Chunked方式实现

/**
 * 使用Chunked 方式传输文件
 * 
 * @param out  面向客户端的输出流
 * @param file 文件
 * @throws IOException
 */
public void transferFileChunked(BufferedOutputStream out, File file) throws IOException {
    RandomAccessFile raf = new RandomAccessFile(file, "r");
    try {
        System.out.println("准备传输 + ");
        out.write("Transfer-Encoding: chunked".getBytes());
        out.write(BREAK_LINE);
        out.write(BREAK_LINE);
        int sizeRead = raf.read(data);

        while (sizeRead > 0) {
            System.out.println("准备传输 + " + String.format("%x", sizeRead));
            out.write(String.format("%x", sizeRead).getBytes());
            out.write(BREAK_LINE);
            out.write(data, 0, sizeRead);
            out.write(BREAK_LINE);
            sizeRead = raf.read(data);
            out.flush();
        }
        out.write(48);
        out.write(BREAK_LINE);
        out.write(BREAK_LINE);
        out.write(BREAK_LINE);
        out.flush();
    } catch (IOException e) {
        e.printStackTrace();
        throw e;
    } finally {
        try {
            raf.close();
        } catch (Exception e) {
        }
    }

}

写在后面


内容
隐藏