okhttp3請求頭中含中文報錯原因及解決方案

最近在負責做一個圖片加載模塊展东,測試過程中反饋一個問題:有兩個測試設備上加載不了圖片。我就納悶了傅寡,我就一個加載圖片模塊怎么還跟機型適配扯上關系了哟绊。然后查了下日志異常如下:


java.lang.IllegalArgumentException: Unexpected char 0x8d2d at 13 in content-disposition value: filename="3.6購買頁.jpg"

at com.bumptech.glide.request.RequestFutureTarget.doGet(RequestFutureTarget.java:189)

at com.bumptech.glide.request.RequestFutureTarget.get(RequestFutureTarget.java:100)

at common.disk.ImageDiskCache.lambda$putCacheImage$0$ImageDiskCache(ImageDiskCache.java:100)

at common.disk.ImageDiskCache$$Lambda$0.run(Unknown Source)

at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)

at java.lang.Thread.run(Thread.java:818)

Caused by: java.lang.IllegalArgumentException: Unexpected char 0x8d2d at 13 in content-disposition value: filename="3.6購買頁.jpg"

at okhttp3.Headers$Builder.checkNameAndValue(Headers.java:283)

at okhttp3.Headers$Builder.add(Headers.java:233)

at okhttp3.internal.http.Http2xStream.readHttp2HeadersList(Http2xStream.java:263)

at okhttp3.internal.http.Http2xStream.readResponseHeaders(Http2xStream.java:149)

at okhttp3.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:723)

at okhttp3.internal.http.HttpEngine.access$200(HttpEngine.java:81)

at okhttp3.internal.http.HttpEngine$NetworkInterceptorChain.proceed(HttpEngine.java:708)

at okhttp3.internal.http.HttpEngine.readResponse(HttpEngine.java:563)

at okhttp3.RealCall.getResponse(RealCall.java:241)

at okhttp3.RealCall$ApplicationInterceptorChain.proceed(RealCall.java:198)

at okhttp3.SNInterceptor.intercept(SNInterceptor.java:62)

at okhttp3.RealCall$ApplicationInterceptorChain.proceed(RealCall.java:187)

at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:160)

at okhttp3.RealCall.execute(RealCall.java:57)

at glide.MOkHttpStreamFetcher.loadData(MOkHttpStreamFetcher.java:51)

at glide.MOkHttpStreamFetcher.loadData(MOkHttpStreamFetcher.java:22)

at com.bumptech.glide.load.engine.DecodeJob.decodeSource(DecodeJob.java:170)

at com.bumptech.glide.load.engine.DecodeJob.decodeFromSource(DecodeJob.java:128)

at com.bumptech.glide.load.engine.EngineRunnable.decodeFromSource(EngineRunnable.java:127)

at com.bumptech.glide.load.engine.EngineRunnable.decode(EngineRunnable.java:106)

at com.bumptech.glide.load.engine.EngineRunnable.run(EngineRunnable.java:58)

at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:423)

at java.util.concurrent.FutureTask.run(FutureTask.java:237)

at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)

at java.lang.Thread.run(Thread.java:818)

at com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor$DefaultThreadFactory$1.run(FifoPriorityThreadPoolExecutor.java:118)

其實從日志上,問題原因已經很明顯门怪,但是查找問題的時候我犯了個錯誤庭敦。就是沒有根據堆棧信息查找問題,這是由于平時發(fā)現問題的時候習慣于定位應用的代碼入口薪缆,而不是查看源碼報錯處。

當時看到這個異常的時候伞广,第一反應是保存文件的時候使用中文出錯拣帽,但是我寫的代碼中保存圖片用的是自己定義的字符串,而這個filename在代碼里根本查不到嚼锄。所以這種情況下這個filename只能是通過圖片url獲取到的减拭,然后我打開chrome調試,可以看到圖片url的響應報頭中有這個東西:


Content-Disposition:filename="3.6購買頁.jpg

Content-disposition是 MIME 協議的擴展区丑,MIME 協議指示 MIME 用戶代理如何顯示附加的文件拧粪。當 Internet Explorer 接收到頭時修陡,它會激活文件下載對話框,它的文件名框自動填充了頭中指定的文件名可霎。

知道了filename是哪里來的之后魄鸦,再去找發(fā)生問題的原因。在我的代碼里我只調用了:


File imageFile = Glide.with(mContext).load(url).downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL).get();

所以當時我的理解是癣朗,glide在加載圖片時內部緩存文件時因為filename報錯拾因。看了半天源碼后旷余,發(fā)現最終調用的是項目中自定義的DataFetcher的loadData方法绢记,然后就是okhttp的正常請求調用了。其實整個調用鏈跟異常日志的堆棧信息是一樣的正卧。okhttp的詳細調用略過蠢熄,最終的問題出現在Http2xStream(這個類是負責處理Http2.0協議的,還有一個Http1xStream類處理Http1.x協議炉旷,這個會根據當前設備是否支持去初始化不同的類签孔,這也是為什么會有請求頭中文報錯只有部分機型存在)的readHttp2HeadersList方法,這里會讀取response的header砾跃,問題在這個調用


headersBuilder.add(name.utf8(), value);

okhttp3.Headers.java


    /** Add a field with the specified value. */

    public Builder add(String name, String value) {

      checkNameAndValue(name, value);

      return addLenient(name, value);

    }

    private void checkNameAndValue(String name, String value) {

      if (name == null) throw new IllegalArgumentException("name == null");

      if (name.isEmpty()) throw new IllegalArgumentException("name is empty");

      for (int i = 0, length = name.length(); i < length; i++) {

        char c = name.charAt(i);

        if (c <= '\u001f' || c >= '\u007f') {

          throw new IllegalArgumentException(String.format(

              "Unexpected char %#04x at %d in header name: %s", (int) c, i, name));

        }

      }

      if (value == null) throw new IllegalArgumentException("value == null");

      for (int i = 0, length = value.length(); i < length; i++) {

        char c = value.charAt(i);

        if (c <= '\u001f' || c >= '\u007f') {

          throw new IllegalArgumentException(String.format(

              "Unexpected char %#04x at %d in %s value: %s", (int) c, i, name, value));

        }

      }

    }

這里就是問題出現的原因骏啰,checkNameAndValue這個方法會對請求頭的name、value進行校驗抽高。

解決思路

既然發(fā)現了出現問題的原因判耕,現在就是找解決方案,其實在網上搜索okhttp請求頭中文翘骂,這個關鍵字也會搜到一些文章壁熄。但是這些給出的解決方案一般都是對請求頭進行轉碼,因為一般這種問題都出現在前端request的時候碳竟,而我碰到的服務端返回的請求頭中帶中文草丧。

出現這問題之后我首先是想讓后端協助解決掉這個問題,但是跟后端溝通過后發(fā)現他也不知道這個filename是哪里來的莹桅,他沒有對這塊進行處理昌执。出于各種原因他也不能去專門修改這個問題,同時他也指出你們使用的框架不支持請求頭中文诈泼,本身就不合理懂拾。我聽他這么一說也挺有道理,就放棄了這個想法铐达。

既然靠后端修改走不通岖赋,我又查了下資料。既然filename有一個名字瓮孙,那肯定是哪里傳過去的唐断,后臺配置圖片都配置的是英文选脊,這中文必然是上傳圖片時存儲在本地的文件名。然后跟產品和運營溝通了一下脸甘,果然這個命名是他們本地的恳啥。然后讓他們修改本地文件名重新上傳了一下,這問題算是解決了斤程。

但是角寸,問題肯定不能到這為止。如果其他人碰到這個問題忿墅,又沒辦法讓在源頭做修改扁藕,那這個問題如何解決?

下面說一下我個人的解決方案:

方案一:使用攔截器(適用于發(fā)起request時)

這個方案比較常規(guī)疚脐,你也可以在出錯的請求發(fā)起時對對應的request請求頭進行轉碼亿柑。也可以在構造OkHttpClient時addInterceptor,然后在intercept中對request統一進行轉碼棍弄。具體代碼就不贅述了望薄,到處都能找到。但是需要注意的是呼畸,在intercept中是無法規(guī)避response中請求頭有中文的痕支,因為出錯的位置在你通過chain.proceed(request)拿到response之前,這點可以在源碼中看到后續(xù)我也會寫文章講okhttp整個流程蛮原。

方案二:反射

這個方案是我自己使用的方案卧须,源于分析代碼的時候,問題出在readHttp2HeadersList的


if (!HTTP_2_SKIPPED_RESPONSE_HEADERS.contains(name)) {

        headersBuilder.add(name.utf8(), value);

      }

這里的add方法儒陨,而我碰到的問題是某一個請求頭會返回中文花嘶,并且客戶端不需要這個請求頭的參數。那既然這樣蹦漠,如果我去修改HTTP_2_SKIPPED_RESPONSE_HEADERS這個List椭员,不就可以實現我需要的功能并且改動最小嗎。具體代碼如下:


private boolean hookOkHttpReadHeader(){

        try {

            Class clz = Class.forName("okhttp3.internal.http.Http2xStream");

            Field field = clz.getField("HTTP_2_SKIPPED_RESPONSE_HEADERS");

            field.setAccessible(true);

            field.set(new ArrayList<>(), HTTP_2_SKIPPED_RESPONSE_HEADERS);

return true;

        } catch (ClassNotFoundException e) {

            e.printStackTrace();

        } catch (NoSuchFieldException e) {

            e.printStackTrace();

        } catch (IllegalAccessException e) {

            e.printStackTrace();

        }

        return false;

    }

此方法適用于客戶端不需要請求頭中的參數的情況笛园。

方案三:方法替換

雖然我自己的問題解決了隘击,但是我還是在想能不能去完美的解決這個問題而不是取巧,有沒有一個方法能夠實現替換Headers中的add方法實現研铆,讓add方法不去調用checkNameAndValue埋同,或者是修改checkNameAndValue的內部實現。

想過之后突然發(fā)現這不就是熱修復中的方法替換蚜印,andfix不就是通過替換方法指針達到修復的目的嗎,這個情況跟我想要實現的一模一樣留量。既然如此窄赋,可以使用andfix的方案解決問題哟冬。但是這樣會導致包體積增大以及兼容性問題,而且就算有源碼實現這個過程也是要耗費大量時間的忆绰,這里只是提供一種思路浩峡。

方案四:自行編譯

這個方案是你自己去pull okhttp源碼,修改對應位置错敢,然后生成依賴供自己使用翰灾。這樣可以完整的規(guī)避這種問題

本文同步發(fā)布在:https://pengsongandroid.github.io/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末稚茅,一起剝皮案震驚了整個濱河市纸淮,隨后出現的幾起案子,更是在濱河造成了極大的恐慌亚享,老刑警劉巖咽块,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異欺税,居然都是意外死亡侈沪,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進店門晚凿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來亭罪,“玉大人,你說我怎么就攤上這事歼秽∮σ郏” “怎么了?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵哲银,是天一觀的道長扛吞。 經常有香客問我,道長荆责,這世上最難降的妖魔是什么滥比? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮做院,結果婚禮上盲泛,老公的妹妹穿的比我還像新娘。我一直安慰自己键耕,他們只是感情好寺滚,可當我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著屈雄,像睡著了一般村视。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上酒奶,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天蚁孔,我揣著相機與錄音奶赔,去河邊找鬼。 笑死杠氢,一個胖子當著我的面吹牛站刑,可吹牛的內容都是我干的。 我是一名探鬼主播鼻百,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼绞旅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了温艇?” 一聲冷哼從身側響起因悲,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎中贝,沒想到半個月后囤捻,有當地人在樹林里發(fā)現了一具尸體醒第,經...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡谦秧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了阵面。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绣否。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡誊涯,死狀恐怖,靈堂內的尸體忽然破棺而出蒜撮,到底是詐尸還是另有隱情暴构,我是刑警寧澤,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布段磨,位于F島的核電站取逾,受9級特大地震影響,放射性物質發(fā)生泄漏苹支。R本人自食惡果不足惜砾隅,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望债蜜。 院中可真熱鬧晴埂,春花似錦、人聲如沸寻定。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽狼速。三九已至琅锻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背恼蓬。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工沫浆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人滚秩。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像淮捆,于是被迫代替她去往敵國和親郁油。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,455評論 2 359

推薦閱讀更多精彩內容