こんにちは。決済システムでエンジニアをやっております hoshino33 です。 今回はPowerShellのInvoke-WebRequestを実行した際に2XX系以外でHTTPステータスコードが返ってきた際にレスポンスボディが取れなかったので、その際に対応した内容になります。
- はじめに
- 2XX系のレスポンス(レスポンスボディなし)
- 2XX系以外のレスポンス(レスポンスボディなし)
- 2XX系以外のレスポンス(レスポンスボディあり)
- 2XX系以外のレスポンス(レスポンスボディあり)を対応してみる
- SkipHttpErrorCheckについて
- まとめ
はじめに
Invoke-WebRequestを使用しつつ、curlでもどのようになるか比較しながら見てみようと思います。 Invoke-WebRequestとcurlは以下のバージョンで確認を行っています。
> $PSVersionTable Name Value ---- ----- PSVersion 5.1.19041.1682 PSEdition Desktop PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...} BuildVersion 10.0.19041.1682 CLRVersion 4.0.30319.42000 WSManStackVersion 3.0 PSRemotingProtocolVersion 2.3 SerializationVersion 1.1.0.1
> curl -V curl 7.58.0 (x86_64-pc-linux-gnu) libcurl/7.58.0 OpenSSL/1.1.1 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
サンプルで使用しているAPIは以下を使用させいただいております。 - https://httpbin.org/#/Status_codes - HTTPステータスコードを確認のため使用 - http://developer.hatena.ne.jp/ja/documents/blog/apis/atom - エラー時のレスポンスを確認のため使用(例で使用しているはてなIDとブログIDはダミーです)
2XX系のレスポンス(レスポンスボディなし)
それではHTTPステータスコードに200が返されるパターンを試してみます。
- Invoke-WebRequest
> Invoke-WebRequest https://httpbin.org/status/200 StatusCode : 200 StatusDescription : OK Content : RawContent : HTTP/1.1 200 OK Connection: keep-alive Access-Control-Allow-Origin: * Access-Control-Allow-Credentials: true Content-Length: 0 Content-Type: text/html; charset=utf-8 Date: Tue, 07 Jun 2022 05:53... Forms : {} Headers : {[Connection, keep-alive], [Access-Control-Allow-Origin, *], [Access-Control-Allow-Credentials, true], [Content-Length, 0]...} Images : {} InputFields : {} Links : {} ParsedHtml : mshtml.HTMLDocumentClass RawContentLength : 0
- curl
> curl -i https://httpbin.org/status/200 HTTP/2 200 date: Tue, 07 Jun 2022 05:55:17 GMT content-type: text/html; charset=utf-8 content-length: 0 server: gunicorn/19.9.0 access-control-allow-origin: * access-control-allow-credentials: true
どちらも想定通りに返ってきているのがわかります。
2XX系以外のレスポンス(レスポンスボディなし)
次はHTTPステータスコードに400が返されるパターンを試してみます。
- Invoke-WebRequest
> Invoke-WebRequest https://httpbin.org/status/400 Invoke-WebRequest : リモート サーバーがエラーを返しました: (400) 要求が不適切です 発生場所 行:1 文字:1 +Invoke-WebRequest https://httpbin.org/status/400 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest]、WebException +FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
- curl
> curl -i https://httpbin.org/status/400 HTTP/2 400 date: Tue, 07 Jun 2022 06:03:41 GMT content-type: text/html; charset=utf-8 content-length: 0 server: gunicorn/19.9.0 access-control-allow-origin: * access-control-allow-credentials: true
Invoke-WebRequestでは2XX系以外なので例外が発生してしまいます。 内容をみるとHTTPステータスコードが400で返ってきているのはわかりますが例外が気になる方は以下のようにすると良さそうです。
> try { > Invoke-WebRequest https://httpbin.org/status/400 > } catch { > $_.Exception.Response > } IsMutuallyAuthenticated : False Cookies : {} Headers : {Connection, Access-Control-Allow-Origin, Access-Control-Allow-Credentials, Content-Length...} SupportsHeaders : True ContentLength : 0 ContentEncoding : ContentType : text/html; charset=utf-8 CharacterSet : utf-8 Server : gunicorn/19.9.0 LastModified : 2022/06/07 15:12:41 StatusCode : BadRequest StatusDescription : BAD REQUEST ProtocolVersion : 1.1 ResponseUri : https://httpbin.org/status/400 Method : GET IsFromCache : False
2XX系以外のレスポンス(レスポンスボディあり)
今回の本題です。 認証を行っていないので、HTTPステータスコードが401で何かしらのレスポンスボディが返ってくる想定です。
- Invoke-WebRequest
> try { > Invoke-WebRequest https://blog.hatena.ne.jp/hoge/hoge.hatenablog.com/atom/entry > } catch { > $_.Exception.Response > } IsMutuallyAuthenticated : False Cookies : {b=$1$Jshjbbb9$vRpgU3FHO9mvRwbF8u9ln0, ek=, sk=198fc3de6792854527b3b3f164bd8ce4b90ad14d} Headers : {Transfer-Encoding, Connection, Vary, Cache-Control...} SupportsHeaders : True ContentLength : -1 ContentEncoding : ContentType : text/html; charset=utf-8 CharacterSet : utf-8 Server : nginx LastModified : 2022/06/07 15:35:52 StatusCode : Unauthorized StatusDescription : Unauthorized ProtocolVersion : 1.1 ResponseUri : https://blog.hatena.ne.jp/hoge/hoge.hatenablog.com/atom/entry Method : GET IsFromCache : False
- curl
> curl -i https://blog.hatena.ne.jp/hoge/hoge.hatenablog.com/atom/entry HTTP/1.1 401 Unauthorized Server: nginx Date: Tue, 07 Jun 2022 06:32:13 GMT Content-Type: text/html; charset=utf-8 Transfer-Encoding: chunked Connection: keep-alive Set-Cookie: b=$1$X0LE8DWV$K/v8SszSGiZ2iTFqi5iMd/; expires=Mon, 02 Jun 2042 06:32:13 GMT; domain=hatena.ne.jp; path=/ Cache-Control: private Vary: Accept-Language,Cookie,Accept-Encoding WWW-Authenticate: Basic realm="Hatena Blog" WWW-Authenticate: WSSE profile="UsernameToken" WWW-Authenticate: OAuth realm="Hatena Blog" Content-Security-Policy-Report-Only: block-all-mixed-content; report-uri https://blog.hatena.ne.jp/api/csp_report P3P: CP="OTI CUR OUR BUS STA" X-Content-Type-Options: nosniff X-Frame-Options: DENY X-Revision: 1fab4f40deb38548e9beab265c9917 X-XSS-Protection: 1 Set-Cookie: ek=; path=/; expires=Tue, 07-Jun-2022 05:32:13 GMT Set-Cookie: sk=a226416ed0f178838a50818cd5b73e44f651c928; path=/ X-Runtime: 0.007296 X-Proxy-Revision: 453c0d2 <p class="error-box">Authorization required</p>
Invoke-WebRequestではHTTPステータスコード(Unauthorized = 401)はわかりますが、レスポンスボディがありません。
curlでの<p class="error-box">Authorization required</p>
の部分。
2XX系以外のレスポンス(レスポンスボディあり)を対応してみる
レスポンスボディを取得するためにはResponseStreamから読み込む必要があるようです。 そのため以下を実行してみます。 ※HTTPステータスコードとレスポンスボディが欲しいため出力時は一部加工しています。
> try { > Invoke-WebRequest https://blog.hatena.ne.jp/hoge/hoge.hatenablog.com/atom/entry > } catch { > $ExceptionResponse = $_.Exception.Response > $Stream = $ExceptionResponse.GetResponseStream() > $Stream.Position = 0 > $Reader = [System.IO.StreamReader]::new($Stream) > $ErrResponse = $Reader.ReadToEnd() > "StatusCode : {0}, Response : {1}" -f $ExceptionResponse.StatusCode.Value__ ,$ErrResponse > } StatusCode : 401, Response : <p class="error-box">Authorization required</p>
SkipHttpErrorCheckについて
5系ではなかったのですが、7系からはSkipHttpErrorCheckオプションが追加され、 以下のようにするとエラーレスポンスまで取得できるようです。
> $PSVersionTable Name Value ---- ----- PSVersion 7.2.4 PSEdition Core GitCommitId 7.2.4 OS Microsoft Windows 10.0.19043 Platform Win32NT PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…} PSRemotingProtocolVersion 2.3 SerializationVersion 1.1.0.1 WSManStackVersion 3.0
> Invoke-WebRequest -SkipHttpErrorCheck https://blog.hatena.ne.jp/hoge/hoge.hatenablog.com/atom/entry StatusCode : 401 StatusDescription : Unauthorized Content : <p class="error-box">Authorization required</p> RawContent : HTTP/1.1 401 Unauthorized Server: nginx Date: Tue, 14 Jun 2022 08:13:52 GMT Transfer-Encoding: chunked Connection: keep-alive Set-Cookie: b=$1$0x52Lrkc$8Bj6eU1PPtlJ8KbVxEJel1; expires=Mon, 09 Jun… Headers : {[Server, System.String[]], [Date, System.String[]], [Transfer-Encoding, System.String[]], [Connection, System.String[]]…} Images : {} InputFields : {} Links : {} RawContentLength : 48 RelationLink : {}
まとめ
7系以前のInvoke-WebRequestを使用する場合は一手間必要でしたが、7系からはSkipHttpErrorCheckオプションを指定することにより簡単に取得できるようになったようです。 また、なんらかの理由で7系に上げられない場合は、WSLをサクッとインストールしてcurlを使用した方が楽そうです。