post方法 request.getInputStream()為空解惑

前言

在SpringMVC web應(yīng)用中爸吮,對(duì)于一個(gè)rest接口所宰,獲取請(qǐng)求參數(shù)我們一般使用@requestParam@requestBody等注解 够傍。對(duì)于表單類型的請(qǐng)求參數(shù)甫菠,有一下幾種獲取方式

  1. @requestParam注解方式
  2. request.getParameter(String name)
  3. request.getInputStream()

前兩種方式其實(shí)是一種方式,@requestParam底層就是利用request.getParameter的原理冕屯。這兩種方式有一個(gè)弊端就是只能一個(gè)個(gè)獲取寂诱,而且必須知道對(duì)方傳過(guò)來(lái)的參數(shù)的key值,如果想要一次性獲取安聘,可以使用request.getInputStream方法獲取一個(gè)inputStream對(duì)象痰洒,然后讀取流里面的數(shù)據(jù)。

//獲取到的數(shù)據(jù)格式key=value以‘&’分隔的形式
age=20&name=faderw

問(wèn)題

但在實(shí)際過(guò)程中浴韭,我們會(huì)發(fā)現(xiàn)通過(guò)request.getInputStream()方式獲取的數(shù)據(jù)為空丘喻。

根據(jù)Servlet規(guī)范,如果同時(shí)滿足下列條件念颈,則請(qǐng)求體(Entity)中的表單數(shù)據(jù)泉粉,將被填充到request的parameter集合中(request.getParameter系列方法可以讀取相關(guān)數(shù)據(jù))

  1. 這是一個(gè)HTTP/HTTPS請(qǐng)求
  2. 請(qǐng)求方法是POST(querystring無(wú)論是否POST都將被設(shè)置到parameter中)
  3. 請(qǐng)求的類型(Content-Type頭)是application/x-www-form-urlencoded
  4. Servlet調(diào)用了getParameter系列方法

這里的表單數(shù)據(jù)已經(jīng)被填充到parameterMap中,不能再通過(guò)getInputStream獲取。

如何解決這個(gè)問(wèn)題呢嗡靡。

實(shí)現(xiàn)

在javax.servlet.http包下面有一個(gè)裝飾器類HttpServletRequestWrapper跺撼,利用這個(gè)裝飾器類,我們可以重新包裝一個(gè)HttpServletRequest對(duì)象讨彼。

public class HttpServletRequestWrapper extends ServletRequestWrapper implements
        HttpServletRequest {

定義一個(gè)裝飾器繼承HttpServletRequestWrapper,streamBody字節(jié)變量用來(lái)保存讀取的數(shù)據(jù)歉井,以便于多次讀取。

public class InputStreamHttpServletRequestWrapper extends HttpServletRequestWrapper{


    private final byte[] streamBody;
    private static final int BUFFER_SIZE = 4096;

   
    public InputStreamHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        byte[] bytes = inputStream2Byte(request.getInputStream());
        if (bytes.length == 0 && RequestMethod.POST.name().equals(request.getMethod())) {
            //從ParameterMap獲取參數(shù)哈误,并保存以便多次獲取
            bytes = request.getParameterMap().entrySet().stream()
                    .map(entry -> {
                        String result;
                        String[] value = entry.getValue();
                        if (value != null && value.length > 1) {
                            result = Arrays.stream(value).map(s -> entry.getKey() + "=" + s)
                                    .collect(Collectors.joining("&"));
                        } else {
                            result = entry.getKey() + "=" + value[0];
                        }

                        return result;
                    }).collect(Collectors.joining("&")).getBytes();
        }

        streamBody = bytes;
    }

    private byte[] inputStream2Byte(InputStream inputStream) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] bytes = new byte[BUFFER_SIZE];
        int length;
        while ((length = inputStream.read(bytes, 0, BUFFER_SIZE)) != -1) {
            outputStream.write(bytes, 0, length);
        }

        return outputStream.toByteArray();
    }


    @Override
    public ServletInputStream getInputStream() throws IOException {
        ByteArrayInputStream inputStream = new ByteArrayInputStream(streamBody);

        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener listener) {

            }

            @Override
            public int read() throws IOException {
                return inputStream.read();
            }
        };
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }
}

聲明一個(gè)帶有HttpServletRequest入?yún)⒌臉?gòu)造器哩至,從該參數(shù)對(duì)象的流中解析數(shù)據(jù),如果沒(méi)有則繼續(xù)從parameterMap中獲取黑滴,然后以key=value&key=value形式拼接憨募。用streamBody接收紧索。然后我們重寫getInputStream方法袁辈,以后每次調(diào)用getInputStream方法,其實(shí)是重新利用streamBody重新new一個(gè)流珠漂,所以可以多次讀取晚缩。

有了裝飾器后,我們就要裝飾目標(biāo)對(duì)象媳危。我們都知道SpringMVC的一次請(qǐng)求會(huì)被一個(gè)個(gè)過(guò)濾器層層調(diào)用荞彼,也就是我們常說(shuō)的責(zé)任鏈模式。利用Filter我們就可以在某個(gè)特定的位置裝飾HttpServletRequest對(duì)象待笑。

public class InputStreamWrapperFilter extends OncePerRequestFilter{

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        ServletRequest servletRequest = new InputStreamHttpServletRequestWrapper(httpServletRequest);

        filterChain.doFilter(servletRequest, httpServletResponse);
    }
}

OncePerRequestFilter這個(gè)過(guò)濾器能夠保證一次請(qǐng)求只經(jīng)過(guò)一次過(guò)濾器鸣皂,所以我們直接繼承該類就行了。

@Bean
@Order(1)
public FilterRegistrationBean inputStreamWrapperFilterRegistration() {
    FilterRegistrationBean registrationBean = new FilterRegistrationBean();
    registrationBean.setFilter(new InputStreamWrapperFilter());
    registrationBean.setName("inputStreamWrapperFilter");
    registrationBean.addUrlPatterns("/*");

    return registrationBean;
}

然后注冊(cè)該過(guò)濾器暮蹂,設(shè)置優(yōu)先級(jí)為1寞缝。Spring Boot 會(huì)按照order值的大小,從小到大的順序來(lái)依次過(guò)濾仰泻。

測(cè)試

我們寫一個(gè)簡(jiǎn)單的rest接口測(cè)試下

@PostMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Object inputStreamTest(HttpServletRequest request) throws Exception {
    String bs = IOUtils.toString(request.getInputStream(), "UTF-8");
    Map<String, String> map = Maps.newHashMapWithExpectedSize(1);
    map.put("data", bs);

    return map;
}

curl命令

curl -X POST \
  http://127.0.0.1:9003/home \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -H 'Postman-Token: bb6e680c-5142-4d27-b930-6efb118a505a' \
  -d 'age=20&name=wangyuxin'

結(jié)果

{
    "data": "age=20&name=wangyuxin"
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末荆陆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子集侯,更是在濱河造成了極大的恐慌被啼,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件棠枉,死亡現(xiàn)場(chǎng)離奇詭異浓体,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)辈讶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門命浴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人荞估,你說(shuō)我怎么就攤上這事咳促≈尚拢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵跪腹,是天一觀的道長(zhǎng)褂删。 經(jīng)常有香客問(wèn)我,道長(zhǎng)冲茸,這世上最難降的妖魔是什么屯阀? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮轴术,結(jié)果婚禮上难衰,老公的妹妹穿的比我還像新娘。我一直安慰自己逗栽,他們只是感情好盖袭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著彼宠,像睡著了一般鳄虱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上凭峡,一...
    開(kāi)封第一講書(shū)人閱讀 51,301評(píng)論 1 301
  • 那天拙已,我揣著相機(jī)與錄音,去河邊找鬼摧冀。 笑死倍踪,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的索昂。 我是一名探鬼主播建车,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼楼镐!你這毒婦竟也來(lái)了癞志?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤框产,失蹤者是張志新(化名)和其女友劉穎凄杯,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體秉宿,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡戒突,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了描睦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片膊存。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出隔崎,到底是詐尸還是另有隱情今艺,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布爵卒,位于F島的核電站虚缎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏钓株。R本人自食惡果不足惜实牡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望轴合。 院中可真熱鬧创坞,春花似錦、人聲如沸受葛。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)奔坟。三九已至携栋,卻和暖如春搭盾,著一層夾襖步出監(jiān)牢的瞬間咳秉,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工鸯隅, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留澜建,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓蝌以,卻偏偏與公主長(zhǎng)得像炕舵,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子跟畅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理咽筋,服務(wù)發(fā)現(xiàn),斷路器徊件,智...
    卡卡羅2017閱讀 134,654評(píng)論 18 139
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架奸攻,建立于...
    Hsinwong閱讀 22,396評(píng)論 1 92
  • 18歲的河妖離開(kāi)她熟悉的小島來(lái)到陌生人的城市,開(kāi)始她向往已久的大學(xué)生活,成日與舞結(jié)伴的她虱痕,生性愛(ài)玩睹耐,舞團(tuán)里...
    披頭士過(guò)馬路閱讀 293評(píng)論 0 1
  • 《柔》 云溪上密風(fēng)舞動(dòng); 松窗下疏雨柔靜部翘。
    自命飛皇Yoes閱讀 501評(píng)論 0 1
  • iOS native 加載 H5 圖片(沙盒加載html圖片) socket的半包硝训,粘包與分包的問(wèn)題 Scoket...
    AgoniNemo閱讀 146評(píng)論 0 0