dropbox_uploader.shがエラーを吐くようになった対応

 Dropboxの操作をCUIで行えるスクリプトdropbox_uploader.shを便利に使用させてもらっています。
github.com

 ですが、最近以下のようにエラー出力され、機能しなくなってしまいました。

$ ~/bin/dropbox_uploader.sh list
 > Listing "/"... FAILED
Some error occured. Please check the log.

 

調査

 一時的なサーバー側 or N/Wの問題かしらと思ったのですが、同じアカウントを使用している別の端末ではこれまで通りに正常に機能しており、特定のマシンのみ上記エラーが発生します。
 "Please check the log."と言われてもログ出力はされていませんが、dropbox_uploader.shは-dオプションを指定するとデバッグ出力がコンソール表示できます。この出力を基に調べました。
 ※前提知識として、dropbox_uploader.shはbashcurlがコアで、curlでPOSTリクエストを使ってdropboxサーバー側APIを叩いて、その結果を評価して後続処理の実行制御する作りになっています。

  • エラーが発生するマシンのデバッグ出力(抜粋)
+ print ' > Getting info... '
+ [[ 0 == 0 ]]
+ echo -ne ' > Getting info... '
 > Getting info... + /usr/bin/curl -X POST -L -s --show-error --globoff -i -o /tmp/du_resp_debug --header 'Authorization: Bearer ****************************************************************' https://api.dropboxapi.com/2/users/get_current_account
+ check_http_response
+ CODE=0
+ case $CODE in
+ grep -q 'HTTP/1.1 400' /tmp/du_resp_debug
+ grep -q '^HTTP/1.1 200 OK' /tmp/du_resp_debug
+ print 'FAILED\n'
+ [[ 0 == 0 ]]
+ echo -ne 'FAILED\n'
FAILED
+ ERROR_STATUS=1
+ remove_temp_files
+ [[ 1 == 0 ]]
+ [[ 1 -ne 0 ]]
+ echo 'Some error occured. Please check the log.'
Some error occured. Please check the log.
+ exit 1
  • エラーが発生しないマシンのデバッグ出力(抜粋)
+ print ' > Getting info... '
+ [[ 0 == 0 ]]
+ echo -ne ' > Getting info... '
 > Getting info... + /usr/bin/curl -X POST -L -s --show-error --globoff -i -o /tmp/du_resp_debug --header 'Authorization: Bearer ****************************************************************' https://api.dropboxapi.com/2/users/get_current_account
+ check_http_response
+ CODE=0
+ case $CODE in
+ grep -q 'HTTP/1.1 400' /tmp/du_resp_debug
+ grep -q '^HTTP/1.1 200 OK' /tmp/du_resp_debug
++ sed -n 's/.*"display_name": "\([^"]*\).*/\1/p' /tmp/du_resp_debug
+ name='Your dropbox name'
…後続処理

 このように https://api.dropboxapi.com/2/users/get_current_account を叩いた後のエラーハンドリングを、curlのレスポンスファイル(/tmp/du_resp_debug)をgrepして、HTTP/1.1の400(異常系)または200(正常系)があるかを判定しているようです。エラーが発生するマシンと発生しないマシンのレスポンスファイルの差異を比べてみると、以下の違いが確認できました。

  • エラーが発生するマシンのレスポンスファイル(抜粋)
HTTP/2 200
server: nginx
…以下省略
  • エラーが発生しないマシンのレスポンスファイル(抜粋)
HTTP/1.1 200 OK
Server: nginx
…以下省略

 というわけで、HTTP/2が使用されていると、dropbox_uploader.shのエラーハンドリングが適切に機能せず、"HTTP/1.1 200 OK"が見つからないためエラーとして判定されてしまうことが判りました。
 

 そもそも、異なるマシンでHTTP1.1だったりHTTP2だったりが使用される基準は何なのかについても調べました。エラーが発生するようになったマシンは最近apt upgradeを実行しており、curlが更新されています。

  • エラーが発生するマシンのcurlバージョン
$ curl --version
curl 7.58.0 (x86_64-pc-linux-gnu) libcurl/7.58.0 OpenSSL/1.1.0g zlib/1.2.11 libidn2/2.0.4 libpsl/0.19.1 (+libidn2/2.0.4) nghttp2/1.30.0 librtmp/2.3
Release-Date: 2018-01-24
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp smb smbs smtp smtps telnet tftp
Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy PSL
  • エラーが発生しないマシンのcurlバージョン
$ curl --version
curl 7.47.0 (arm-unknown-linux-gnueabihf) libcurl/7.47.0 GnuTLS/3.4.10 zlib/1.2.8 libidn/1.32 librtmp/2.3
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp smb smbs smtp smtps telnet tftp
Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP UnixSockets

 エラーが発生する方のマシンのcurlにはFeaturesにHTTP2が含まれていることから、最近のx86-64向けのcurlではHTTP2が利用可能ならHTTP1.1より優先的にHTTP2を使うような改修が入ったのではないかと考えられます*1
 

対応方法

 エラーハンドリング部に"HTTP/2 200"も正常系として処理されるよう回収するのが正攻法だと思います。が、16箇所はありそうです。

$ grep "HTTP/1.1 200 OK" ~/bin/dropbox_uploader.sh | wc -l
16

 とりあえず動かすだけなら上記の正常系16箇所ですが、異常系もHTTP/2に対応した改修が必要ですので、16箇所の修正だけでは済みません。

 同様の修正とは言え、何か所も直すのは面倒なので、効率的な修正方法がないか検討しました。
 curlバイナリを指定する変数$CURL_BINに、以下のようにhttp1.1の使用を強制するオプションも一緒に指定してしまえば、2箇所の修正だけで済みます*2

$ diff dropbox_uploader.original.sh dropbox_uploader.modified.sh
32c32
< CURL_BIN="/usr/bin/curl"
---
> CURL_BIN="/usr/bin/curl --http1.1"
148c148
<     CURL_BIN="curl"
---
>     CURL_BIN="curl --http1.1"

 

動作確認

 上記修正後、これまで通りに正常に機能するようになったことが確認できています。
 



以上。

*1: Change log : Bionic (18.04) : curl package : Ubuntu からはそれらしい内容は見当たらないのですが。

*2:もちろんHTTP/2のメリットは得られませんが、従来通りにHTTP1.1として動きます。