1. 程式人生 > >搞清tomcat中的編解碼

搞清tomcat中的編解碼

    經常會被亂碼問題攪得頭暈腦脹。事實上,亂碼問題涉及的地方比較多,所以常常有了問題也很難定位,比如,可以發生在容器,可以發生在MVC框架,可以發生在資料庫,可以發生在響應等等。

    這裡分析一下tomcat中是如何編解碼的。

    以"http://localhost:8080/測試?網路=程式設計"為例,可以將tomcat中編解碼分解為這麼幾個地方:

    1. pathInfo.即“測試”這個部分

    2. queryParameter,即”網路=程式設計“這個部分

    3. http header,即瀏覽器傳送的http頭部分

    4. requestBody,http正文部分,即post的正文部分

    1. pathInfo,Http11Processor中的process方法會呼叫InternelInputBuffer來解析請求URL(inputBuffer.parseRequestLine)以及請求頭(inputBuffer.parseHeaders),但是這裡並不是解碼的地方。

public void process(Socket theSocket)
        throws IOException {
        ...
                inputBuffer.parseRequestLine();
                request.setStartTime(System.currentTimeMillis());
                keptAlive = true;
                if (disableUploadTimeout) {
                    socket.setSoTimeout(soTimeout);
                } else {
                    socket.setSoTimeout(timeout);
                }
                // Set this every time in case limit has been changed via JMX
                request.getMimeHeaders().setLimit(endpoint.getMaxHeaderCount());
                inputBuffer.parseHeaders();
        ...
    }

     真正解碼的地方是CoyoteAdapter的convertURI

protected void convertURI(MessageBytes uri, Request request) 
        throws Exception {

        ByteChunk bc = uri.getByteChunk();
        int length = bc.getLength();
        CharChunk cc = uri.getCharChunk();
        cc.allocate(length, -1);

        String enc = connector.getURIEncoding();
        if (enc != null) {
            B2CConverter conv = request.getURIConverter();
            try {
                if (conv == null) {
                    conv = new B2CConverter(enc);
                    request.setURIConverter(conv);
                }
            } catch (IOException e) {
                // Ignore
                log.error("Invalid URI encoding; using HTTP default");
                connector.setURIEncoding(null);
            }
            if (conv != null) {
                try {
                    conv.convert(bc, cc);
                    uri.setChars(cc.getBuffer(), cc.getStart(), 
                                 cc.getLength());
                    return;
                } catch (IOException e) {
                    log.error("Invalid URI character encoding; trying ascii");
                    cc.recycle();
                }
            }
        }

        // Default encoding: fast conversion
        byte[] bbuf = bc.getBuffer();
        char[] cbuf = cc.getBuffer();
        int start = bc.getStart();
        for (int i = 0; i < length; i++) {
            cbuf[i] = (char) (bbuf[i + start] & 0xff);
        }
        uri.setChars(cbuf, 0, length);

    }

    而這裡的解碼使用的是connector的URIEncoding,所以pathInfo的解碼可以通過配置server.xml中的URIEncoding來改變。

    2. queryParameter部分,這裡其實有幾個地方可以控制,首先,我們還是找到解碼queryParameter的地方。在呼叫request.getParameter時最終會呼叫到coyote內部的Parameter中的handleQueryParameters方法,可以看到這裡的queryStringEncoding。

public void handleQueryParameters() {
        if( didQueryParameters ) return;

        didQueryParameters=true;

        if( queryMB==null || queryMB.isNull() )
            return;
        
        if(log.isDebugEnabled()) {
            log.debug("Decoding query " + decodedQuery + " " +
                    queryStringEncoding);
        }

        try {
            decodedQuery.duplicate( queryMB );
        } catch (IOException e) {
            // Can't happen, as decodedQuery can't overflow
            e.printStackTrace();
        }
        processParameters( decodedQuery, queryStringEncoding );
    }

    queryStringEncoding是由什麼地方決定的呢?事實上,有幾個地方決定。第一個是CoyoteAdapter中的service方法,另外就是FormAuthenticator,這兩個地方都使用了connector.getURIEncoding()。

public void service(org.apache.coyote.Request req, 
    	                org.apache.coyote.Response res)
        throws Exception {

        if (request == null) {

            ...

            // Set query string encoding
            req.getParameters().setQueryStringEncoding
                (connector.getURIEncoding());
<span style="white-space:pre">	</span>}
}

    也就是說跟pathInfo是一樣的,但是千萬不要以為就這樣了,其實還有另一個地方會讓整個事情變得很奇怪。在呼叫request.getParameter時,事實上會先呼叫parseParameters方法,然後才呼叫handleQueryParameters,而parseParameters就是第三個設定queryStringEncoding的地方。getCharacterEncoding首先會去找request中設定的charEncoding,找不到就去找requestHeader中contentType的編碼,還找不到就返回null,這時如果在server.xml中設定了useBodyEncodingForURI=true,則queryStringEncoding編碼就會變成預設編碼,即IS08859-1;而考慮另一種情況,如果contentType能找到這個編碼(如UTF-8),則queryStringEncoding跟隨contentType。

所以,結論是,queryStringEncoding編碼的優先順序是,第一是隨contentType,第二隨URIEncoding(即沒有設定contentType編碼,同時也沒有設定useBodyEncodingForURI),第三則是預設編碼(即沒有設定contentType,設定了useBodyEncodingForURI=true)

protected void parseParameters() {

        parametersParsed = true;

        Parameters parameters = coyoteRequest.getParameters();
        // Set this every time in case limit has been changed via JMX
        parameters.setLimit(getConnector().getMaxParameterCount());

        // getCharacterEncoding() may have been overridden to search for
        // hidden form field containing request encoding
        String enc = getCharacterEncoding();

        boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI();
        if (enc != null) {
            parameters.setEncoding(enc);
            if (useBodyEncodingForURI) {
                parameters.setQueryStringEncoding(enc);
            }
        } else {
            parameters.setEncoding
                (org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);
            if (useBodyEncodingForURI) {
                parameters.setQueryStringEncoding
                    (org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);
            }
        }

}

    3. httpheader, 在InternalInputBuffer的parseHeader中解析,最終會呼叫到ByteChunk的toStringInternal,裡面用到的是DEFAULT_CHARSET,這個預設字符集就是ISO8859-1,意味著不能更改httpheader

public String toStringInternal() {
        if (charset == null) {
            charset = DEFAULT_CHARSET;
        }
        // new String(byte[], int, int, Charset) takes a defensive copy of the
        // entire byte array. This is expensive if only a small subset of the
        // bytes will be used. The code below is from Apache Harmony.
        CharBuffer cb;
        cb = charset.decode(ByteBuffer.wrap(buff, start, end-start));
        return new String(cb.array(), cb.arrayOffset(), cb.length());
    }

    4. post中的引數正是上面解析queryStringEncoding中的parameters,也就是說post請求仍然是contentType中的編碼方式優先,其次就是預設的ISO8859-1。

    到這裡,tomcat的編碼基本上算是分析完了。但是編碼問題涉及的點太多,比如資料庫,可以修改資料庫的編碼或者jdbc連線時指定編碼;比如一些框架,如springmvc中的ResponseBody就硬編碼了ISO8859-1,可以換用ResponseEntity,或者Response.getWriter直接輸出。總之,查到什麼地方有問題,才能對症下藥。

相關推薦

搞清tomcat解碼

    經常會被亂碼問題攪得頭暈腦脹。事實上,亂碼問題涉及的地方比較多,所以常常有了問題也很難定位,比如,可以發生在容器,可以發生在MVC框架,可以發生在資料庫,可以發生在響應等等。     這裡分析一下tomcat中是如何編解碼的。     以"http://localhost:8080/測試?網路=程式

Tomcat web 解碼過程

【但是】如果server.xml中設定URIEncoding="utf-8",沒有設定useBodyEncodingForURI="true",那麼,會使用utf-8對16進位制的引數進行解碼(三個位元組對應一個字元),這時候request.getParameter()獲得亂碼的中文引數,並且通過new S

關於Tomcat上請求的解碼問題

tomcat 編碼最近翻閱《深入分析 Java Web 技術內幕》(作者:許令波),關於Tomcat上Web請求的編解碼問題,少了一個小點,可能影響了部分讀者的理解,我特意查證了一下,特總結如下:1. 請求的PathInfo部分用Tomcat的Connector元素的URIEncoding屬性指定的編碼來解碼

實際項目前後端傳輸字符串URL解碼過程遇到的一些問題

component put 傳輸 之間 body unicode編碼 方式 gpo 項目 線上版本(包括12.2,12.3版本)中,參照過濾條件在傳輸過程中經過了URL編碼及解碼過程,前後端使用的API之間的差異導致一些問題,現記錄如下: 前端URL編碼API en

Android使用commons-codec-1.6.jar 進行Base64解碼出現的問題

deb 分享 == 通過 HR common jar hive java 編碼時出現異常: java.lang.NoSuchMethodError: No static method encodeBase64String([B)Ljava/lang/String; i

在C語言使用Libb64進行Base64解碼

tar 語言 number const string ken doc get eof Libb64下載地址http://sourceforge.net/projects/libb64 以下為Demo CLibb64Demo.c #include <stdio.h&g

python的字符串編碼問題——4.unicode解碼(以實際工作遇到的韓文編碼為例)

兼容 技術分享 range window下 byte 分享 pos osi eba 韓文unicode編解碼 問題是這樣,工作中遇到有韓文數據出現亂碼,說是unicode碼。 類似這樣: id name 323 52186863 149 6363

使用libpng直接在記憶體對資料進行png解碼

由於工作需要,需要在記憶體中直接對資料進行png編碼,然後再解碼以測試其解碼的速度,與現有的影象壓縮方法進行比較。由於初次接觸libpng,而網上這種直接在記憶體中操作的文章並不多,從頭學習要花不少的時間。鑑於此,我藉助第3方庫:opencv庫,來學習opencv是怎麼在記憶體中對資料進行操作的(open

Netty實現MessagePack解碼器以及解決粘包問題-參考netty權威指南2

首先maven需要增加依賴 <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId>

基於tomcat的java web專案的請求響應的解碼問題

在一開始寫java web專案的時候,基本上每個人都不可避免地會遇到亂碼的問題,一般我們的解決方法都是這樣的:百度一下java web亂碼的常用解決方法,然後根據症狀找最符合的一個個去試,然後ok完事,然而等到下一次出現相同問題的時候,又得去翻資料。如此治標不治本,實在浪

在Qt移植VPU解碼程式時遇到的問題

在使用freescale開發板實現VPU的硬編碼過程中,將測試程式中關於vpu編碼函式移植到Qt中,在pro檔案中包含 "vpu_lib.h"、"vpu_io.h"的檔案路徑以及對應的連結庫檔案:   INCLUDEPATH  += "/mnt/hgfs/window_

JSURL引數的解碼

HTML中的$("form").serialize()函式,在submit按鈕點選時,將form表單中含有name的input整理成一個“name=aaa&pass=bbb”這樣的字串,使用g

[紹棠_Swift] Swift使用Base64解碼

/// swift Base64處理 /**      *   編碼      */ func base64Encoding(plainString:String)->String

9.C++的base64解碼實現

#include <string> #include <iostream> #include<stdio.h> using namespace std; std::string base64_encode(unsigned char con

ubuntu下c++base64解碼測試和圖片解碼測試

全棧工程師開發手冊 (作者:欒鵬) 字元陣列的base64編解碼 base64.h #include <string> std::string base64_encode(unsigned char const* , unsigned in

url關於解碼加號和空格的問題

今天遇到一個問題,URL中的加號傳到後臺之後變成了空格 BNn+Y6xKvmejeJmu9sS2OnRJwYhHtYXScG2ol17EUhg1oeSFE5btrT4Eh04QiwIf變成了BNn Y

Android視訊編輯器(五)音訊解碼、從視訊分離音訊、音訊混音、音訊音量調節等

/** * 歸一化混音 * */ public static byte[] normalizationMix(byte[][] allAudioBytes){ if (allAudioBytes == null || allAudioBytes.length

mina自定義解碼器接收處理byte陣列(同時解決資料傳輸的粘包、缺包問題)

我們在自定義傳輸協議時,通常都是採用位元組陣列的方式進行傳送,如何正確接收和解碼byte陣列? 假設我們自定義了傳輸協議: 位元組陣列的前4個位元組是要傳輸的資料長度,後面跟資料。我們用mina可以這樣處理 1.自定義編碼器ByteArrayEncoder.java imp

Netty 的訊息解析和解碼

本篇內容主要梳理一下 Netty 中編解碼器的邏輯和編解碼器在 Netty 整個鏈路中的位置。 前面我們在分析 ChannelPipeline 的時候說到入站和出站事件的處理都在 pipeline 中維護著,通過list的形式將處理事件的 handler 按照先後關係儲存為一個列表,有對應的事件過來就按照列