国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|JavaScript|易語言|

服務器之家 - 編程語言 - Java教程 - springboot 中 inputStream 神秘消失之謎(終破)

springboot 中 inputStream 神秘消失之謎(終破)

2021-08-07 23:48老馬嘯西風 Java教程

這篇文章主要介紹了springboot 中 inputStream 神秘消失之謎,為了能夠把這個問題說明,我們首先需要從簡單的http調用說起,通過設置body等一些操作,具體實現代碼跟隨小編一起看看吧

序言

最近小明接手了前同事的代碼,意料之外、情理之中的遇到了坑。

為了避免掉入同一個坑兩次,小明決定把這個坑記下來,并在坑前立一個大牌子,避免其他小伙伴掉進去。

springboot 中 inputStream 神秘消失之謎(終破)

httpclient 模擬調用

為了把這個問題說明,我們首先從最簡單的 http 調用說起。

設置 body

服務端

服務端的代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@controller
@requestmapping("/")
public class reqcontroller {
 
    @postmapping(value = "/body")
    @responsebody
    public string body(httpservletrequest httpservletrequest) {
        try {
            string body = streamutil.tostring(httpservletrequest.getinputstream());
            system.out.println("請求的 body: " + body);
 
            // 從參數中獲取
            return body;
        } catch (ioexception e) {
            e.printstacktrace();
            return e.getmessage();
        }
    }
 
}

java 客戶端要如何請求才能讓服務端讀取到傳遞的 body 呢?

客戶端

這個問題一定難不到你,實現的方式有很多種。

我們以 apache httpclient 為例:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//post請求,帶集合參數
public static string post(string url, string body) {
    try {
        // 通過httppost來發送post請求
        httppost httppost = new httppost(url);
        stringentity stringentity = new stringentity(body);
        // 通過setentity 將我們的entity對象傳遞過去
        httppost.setentity(stringentity);
        return execute(httppost);
    } catch (unsupportedencodingexception e) {
        throw new runtimeexception(e);
    }
}
 
//執行請求返回響應數據
private static string execute(httprequestbase http) {
    try {
        closeablehttpclient client = httpclients.createdefault();
        // 通過client調用execute方法
        closeablehttpresponse response = client.execute(http);
        //獲取響應數據
        httpentity entity = response.getentity();
        //將數據轉換成字符串
        string str = entityutils.tostring(entity, "utf-8");
        //關閉
        response.close();
        return str;
    } catch (ioexception e) {
        throw new runtimeexception(e);
    }
}

可以發現 httpclient 封裝之后還是非常方便的。

我們設置 setentity 為對應入參的 stringentity 即可。

測試

為了驗證正確性,小明本地實現了一個驗證方法。

?
1
2
3
4
5
6
7
8
9
10
11
12
@test
public void bodytest() {
    string url = "http://localhost:8080/body";
    string body = buildbody();
    string result = httpclientutils.post(url, body);
 
    assert.assertequals("body", result);
}
 
private string buildbody() {
    return "body";
}

很輕松,小明漏出了龍王的微笑。

設置 parameter

服務端

小明又看到有一個服務端的代碼實現如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@postmapping(value = "/param")
@responsebody
public string param(httpservletrequest httpservletrequest) {
    // 從參數中獲取
    string param = httpservletrequest.getparameter("id");
    system.out.println("param: " + param);
    return param;
}
 
private map<string,string> buildparammap() {
    map<string,string> map = new hashmap<>();
    map.put("id", "123456");
 
    return map;
}

所有的參數是通過 getparameter 方法獲取,應該如何實現呢?

客戶端

這個倒也不難,小明心想。

因為以前很多代碼都是這樣實現的,于是 ctrl+cv 搞定了下面的代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//post請求,帶集合參數
public static string post(string url, map<string, string> parammap) {
    list<namevaluepair> namevaluepairs = new arraylist<>();
    for (map.entry<string, string> entry : parammap.entryset()) {
        namevaluepair pair = new basicnamevaluepair(entry.getkey(), entry.getvalue());
        namevaluepairs.add(pair);
    }
    return post(url, namevaluepairs);
}
 
//post請求,帶集合參數
private static string post(string url, list<namevaluepair> list) {
    try {
        // 通過httppost來發送post請求
        httppost httppost = new httppost(url);
        // 我們發現entity是一個接口,所以只能找實現類,發現實現類又需要一個集合,集合的泛型是namevaluepair類型
        urlencodedformentity formentity = new urlencodedformentity(list);
        // 通過setentity 將我們的entity對象傳遞過去
        httppost.setentity(formentity);
        return execute(httppost);
    } catch (exception exception) {
        throw new runtimeexception(exception);
    }
}

如此是最常用的 parammap,便于構建;和具體的實現方式脫離,也便于后期拓展。

servlet 標準

urlencodedformentity 看似平平無奇,表示這是一個 post 表單請求。

里面還涉及到 servlet 3.1 的一個標準,必須滿足下面的標準,post 表單的 parameter 集合才可用。

1. 請求是 http 或 https

2. 請求的方法是 post

3. content type 為: application/x-www-form-urlencoded

4. servlet 已經在 request 對象上調用了相關的 getparameter 方法。

當以上條件不滿足時,post 表單的數據并不會設置到 parameter 集合中,但依然可以通過 request 對象的 inputstream 來獲取。

當以上條件滿足時,post 表單的數據在 request 對象的 inputstream 將不再可用了。

這是很重要的一個約定,導致很多小伙伴比較蒙圈。

測試

于是,小明也寫好了對應的測試用例:

?
1
2
3
4
5
6
7
8
9
@test
public void paramtest() {
    string url = "http://localhost:8080/param";
 
    map<string,string> map = buildparammap();
    string result = httpclientutils.post(url, map);
 
    assert.assertequals("123456", result);
}

如果談戀愛能像編程一樣,那該多好。

springboot 中 inputStream 神秘消失之謎(終破)

小明想著,卻不由得眉頭一皺,發現事情并不簡單。

設置 parameter 和 body

服務端

有一個請求的入參是比較大,所以放在 body 中,其他參數依然放在 paramter 中。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@postmapping(value = "/paramandbody")
@responsebody
public string paramandbody(httpservletrequest httpservletrequest) {
    try {
        // 從參數中獲取
        string param = httpservletrequest.getparameter("id");
        system.out.println("param: " + param);
        string body = streamutil.tostring(httpservletrequest.getinputstream());
        system.out.println("請求的 body: " + body);
        // 從參數中獲取
        return param+"-"+body;
    } catch (ioexception e) {
        e.printstacktrace();
        return e.getmessage();
    }
}

其中 streamutil#tostring 是一個對流簡單處理的工具類。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * 轉換為字符串
 * @param inputstream 流
 * @return 結果
 * @since 1.0.0
 */
public static string tostring(final inputstream inputstream)  {
    if (inputstream == null) {
        return null;
    }
    try {
        int length = inputstream.available();
        final reader reader = new inputstreamreader(inputstream, standardcharsets.utf_8);
        final chararraybuffer buffer = new chararraybuffer(length);
        final char[] tmp = new char[1024];
        int l;
        while((l = reader.read(tmp)) != -1) {
            buffer.append(tmp, 0, l);
        }
        return buffer.tostring();
    } catch (exception exception) {
        throw new runtimeexception(exception);
    }
}

客戶端

那么問題來了,如何同時在 httpclient 中設置 parameter 和 body 呢?

機智的小伙伴們可以自己先嘗試一下。

小明嘗試了多種方法,發現一個殘酷的現實—— httppost 只能設置一個 entity,也嘗試看了各種子類,然并luan。

就在小明想要放棄的時候,小明忽然想到,paramter 完全可以通過拼接 url 的方式實現。

也就是我們把 parameter 和 url 并且為一個新的 url,body 和以前設置方式一樣。

實現代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//post請求,帶集合參數
public static string post(string url, map<string, string> parammap,
                          string body) {
    try {
        list<namevaluepair> namevaluepairs = new arraylist<>();
        for (map.entry<string, string> entry : parammap.entryset()) {
            namevaluepair pair = new basicnamevaluepair(entry.getkey(), entry.getvalue());
            namevaluepairs.add(pair);
        }
 
        // 構建 url
        //構造請求路徑,并添加參數
        uri uri = new uribuilder(url).addparameters(namevaluepairs).build();
 
        //構造httpclient
        closeablehttpclient httpclient = httpclients.createdefault();
        // 通過httppost來發送post請求
        httppost httppost = new httppost(uri);
        httppost.setentity(new stringentity(body));
 
        // 獲取響應
        // 通過client調用execute方法
        closeablehttpresponse response = httpclient.execute(httppost);
        //獲取響應數據
        httpentity entity = response.getentity();
        //將數據轉換成字符串
        string str = entityutils.tostring(entity, "utf-8");
        //關閉
        response.close();
        return str;
    } catch (urisyntaxexception | ioexception | parseexception e) {
        throw new runtimeexception(e);
    }
}

這里通過 new uribuilder(url).addparameters(namevaluepairs).build() 構建新的 url,當然你可以使用 &key=value 的方式自己拼接。

測試代碼

?
1
2
3
4
5
6
7
8
9
@test
public void paramandbodytest() {
    string url = "http://localhost:8080/paramandbody";
    map<string,string> map = buildparammap();
    string body = buildbody();
    string result = httpclientutils.post(url, map, body);
 
    assert.assertequals("123456-body", result);
}

測試通過,非常完美。

新的征程

當然,一般的文章到這里就該結束了。

不過上面并不是本文的重點,我們的故事才剛剛開始。

日志需求

大雁飛過,天空一定會留下他的痕跡。

程序更應如此。

為了方便的跟蹤問題,我們一般都是對調用的入參進行日志留痕。

為了便于代碼拓展和可維護性,小明當然采用攔截器的方式。

日志攔截器

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import org.slf4j.logger;
import org.slf4j.loggerfactory;
import org.springframework.stereotype.component;
import org.springframework.util.streamutils;
import org.springframework.web.servlet.handlerinterceptor;
import org.springframework.web.servlet.modelandview;
 
import javax.servlet.http.httpservletrequest;
import javax.servlet.http.httpservletresponse;
import java.nio.charset.standardcharsets;
import java.util.enumeration;
 
/**
 * 日志攔截器
 
 * @author 老馬嘯西風
 * @since 1.0.0
 */
@component
public class loghandlerinterceptor implements handlerinterceptor {
 
    private logger logger = loggerfactory.getlogger(loghandlerinterceptor.class);
 
    @override
    public boolean prehandle(httpservletrequest httpservletrequest,
                             httpservletresponse httpservletresponse, object o) throws exception {
        // 獲取參數信息
        enumeration<string> enumeration = httpservletrequest.getparameternames();
        while (enumeration.hasmoreelements()) {
            string paraname = enumeration.nextelement();
            logger.info("param name: {}, value: {}", paraname, httpservletrequest.getparameter(paraname));
        }
 
        // 獲取 body 信息
        string body = streamutils.copytostring(httpservletrequest.getinputstream(), standardcharsets.utf_8);
        logger.info("body: {}", body);
 
        return true;
    }
 
    @override
    public void posthandle(httpservletrequest httpservletrequest,
                           httpservletresponse httpservletresponse, object o, modelandview modelandview) throws exception {
 
    }
 
    @override
    public void aftercompletion(httpservletrequest httpservletrequest,
                                httpservletresponse httpservletresponse, object o, exception e) throws exception {
 
    }
 
}

非常的簡單易懂,輸出入參中的 parameter 參數和 body 信息。

然后指定一下生效的范圍:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@configuration
public class springmvcconfig extends webmvcconfigureradapter {
 
    @autowired
    private loghandlerinterceptor loghandlerinterceptor;
 
    @override
    public void addinterceptors(interceptorregistry registry) {
        registry.addinterceptor(loghandlerinterceptor)
                .addpathpatterns("/**");
 
        super.addinterceptors(registry);
    }
 
}

所有的請求都會生效。

我的 inputstream 呢?

小伙伴們覺得剛才的日志攔截器有沒有問題?

如果有,又應該怎么解決呢?

小明寫完心想一切順利,一運行測試用例,整個人都裂開了。

所有 controller 方法中的 httpservletrequest.getinputstream() 的內容都變成空了。

是誰?偷走了我的 inputstream?

springboot 中 inputStream 神秘消失之謎(終破)

轉念一想,小明發現了問題所在。

肯定是自己剛才新增的日志攔截器有問題,因為 stream 作為流只能被讀取一遍,日志中讀取一遍之后,后面就讀不到了。

可是日志中必須要輸出,那應該怎么辦呢?

遇事不決

遇事不決,技術問 google,八卦去圍脖。

于是小明去查了一下,解決方案也比較直接,重寫。

重寫 httpservletrequestwrapper

首先重寫 httpservletrequestwrapper,把每次讀取的流信息保存起來,便于重復讀取。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
 * @author binbin.hou
 * @since 1.0.0
 */
public class myhttpservletrequestwrapper extends httpservletrequestwrapper {
 
    private byte[] requestbody = null;//用于將流保存下來
 
    public myhttpservletrequestwrapper(httpservletrequest request) throws ioexception {
        super(request);
        requestbody = streamutils.copytobytearray(request.getinputstream());
    }
 
 
    @override
    public servletinputstream getinputstream() {
        final bytearrayinputstream bais = new bytearrayinputstream(requestbody);
        return new servletinputstream() {
            @override
            public int read() {
                return bais.read();  // 讀取 requestbody 中的數據
            }
 
            @override
            public boolean isfinished() {
                return false;
            }
 
            @override
            public boolean isready() {
                return false;
            }
 
            @override
            public void setreadlistener(readlistener readlistener) {
            }
        };
    }
 
    @override
    public bufferedreader getreader() throws ioexception {
        return new bufferedreader(new inputstreamreader(getinputstream()));
    }
 
}

實現 filter

我們上面重寫的 myhttpservletrequestwrapper 什么時候生效呢?

我們可以自己實現一個 filter,對原有的請求進行替換:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import org.springframework.stereotype.component;
 
import javax.servlet.*;
import javax.servlet.http.httpservletrequest;
import java.io.ioexception;
 
/**
 * @author binbin.hou
 * @since 1.0.0
 */
@component
public class httpservletrequestreplacedfilter implements filter {
 
    @override
    public void destroy() {}
 
    @override
    public void dofilter(servletrequest request, servletresponse response,
                         filterchain chain) throws ioexception, servletexception {
        servletrequest requestwrapper = null;
 
        // 進行替換
        if(request instanceof httpservletrequest) {
            requestwrapper = new myhttpservletrequestwrapper((httpservletrequest) request);
        }
 
        if(requestwrapper == null) {
            chain.dofilter(request, response);
        } else {
            chain.dofilter(requestwrapper, response);
        }
    }
    @override
    public void init(filterconfig arg0) throws servletexception {}
 
}

然后就可以發現一切都好起來了,小明嘴角又漏出了龍王的微笑。

小結

文中對原始問題進行了簡化,實際遇到這個問題的時候,直接就是一個攔截器+參數和body的請求。

所以整個問題排查起來有些浪費時間。

不過浪費的時間如果沒有任何反思,那就是真的浪費了。

最核心的兩點在于:

(1)對于 servlet 標準的理解。

(2)對于流讀取的理解,以及一些 spring 的相關知識。

到此這篇關于springboot 中 inputstream 神秘消失之謎的文章就介紹到這了,更多相關springboot 中 inputstream 內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://www.cnblogs.com/houbbBlogs/p/15110669.html

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 成人精品一区二区 | 日韩视频一 | 久久九| 曰韩免费视频 | 韩国精品一区 | 国产激情网址 | 91av免费在线观看 | 在线不卡a资源高清 | 激情国产 | 日韩电影免费在线观看 | 婷婷中文字幕 | 美女视频一区二区三区 | 高清视频一区 | 午夜专区 | 激情综合婷婷 | 天堂资源库 | 色8888www视频在线观看 | 成年人在线观看视频 | 亚洲成人av在线 | 日韩一级片 | 国产欧美日韩精品一区 | 日韩欧美中文字幕在线视频 | 国产精品视屏 | 亚洲一区电影 | 精品一区二区三区免费 | 午夜国产精品视频 | 草草成人 | 一本久道久久综合狠狠爱 | 久久亚洲一区二区三区明星换脸 | 国产一区二区三区视频 | 天堂色 | 狠狠干av | 黄色a站 | 91精品久久久久久9s密挑 | 欧美一区永久视频免费观看 | 69国产精品成人96视频色 | 黄色网页观看 | 亚洲精品国产综合 | 久久久国产精品入口麻豆 | 综合网伊人 | 在线观看精品91福利 |