curl/PowerShellでHTTPアクセスいろいろ

よくわからなくなるのでメモ

- curl
    - 主に7.3で確認
- PowerShell
    - 主にversion5(Windows10)で確認 (Windows7標準のver2はInvoke-WebRequestはない)

---

更新履歴

- 2019.02.13: リダイレクトについて追加
- 2019.02.08: 暗号化プロトコルの指定について追記
- 2018.09.26: HTTPS通信時の証明書の検証を無視する場合を追加


# GETする(基本)

- curl
    - `curl https://www.google.com`
- PowerShell
    - `Invoke-WebRequest https://www.google.com`

# GETの結果をファイル出力

- curl
    - `curl https://www.google.com -o output.log`
    - ファイル名まであるURLで、保存するファイル名も同じでよければ`-O`も使える
        - `curl -O https://ftp.gnu.org/gnu/wget/wget-1.19.5.tar.gz`
- PowerShell
    - `Invoke-WebRequest https://www.google.com -OutFile output.txt`

`Invoke-WebRequest`で https://ftp.gnu.org への接続がエラーになる件は[後述](#暗号化プロトコルを指定してtls12でアクセス)

# リダイレクト制御

- curl
    - curlはデフォルトではリダイレクトせずにレスポンス302等が返って終了する
    - ファイルのダウンロードなどでリダイレクトも処理してほしい場合は`-L`を使う
    - `curl -LO https://github.com/zaki-lknr/javaee-memoapp2/archive/v1.0.0.tar.gz`
- PowerShell
    - curlと違ってPowerShellではリダイレクトは勝手に処理される
    - リダイレクトを制限したい場合は`-MaximumRedirection`に最大リダイレクト数をセットする(禁止するには0・デフォルトは5)
    - `Invoke-WebRequest http://www.jp-z.jp/perlmemo -MaximumRedirection 0`
    - ただし「既定の数を超えたリダイレクトのレスポンスがあった」という状態になるため、エラー表示となる

- [powershell curl and WebRequest both follow redirects - Stack Overflow](https://stackoverflow.com/questions/41156458/powershell-curl-and-webrequest-both-follow-redirects)

# 進捗の非表示

- curl
    - `curl -s http://www.example.org/`
    - `-s`(`--silent`)を使うとエラーの表示も出なくなるため`-S`(`--show-error`)を併用すると良い
        - `curl -sS http://www.example.org/`
- PowerShell
    - オプションとしてはない

        ```powershell
        $progressPreference = 'silentlyContinue' # progress表示のoff
        Invoke-WebRequest http://www.example.org/
        $progressPreference = 'Continue'         # progress表示のon
        ```

- [powershell - Invoke-WebRequestの進行を非表示](https://stackoverrun.com/ja/q/5119420)


# HTTPのリクエストヘッダとレスポンスヘッダを確認する

- curl
    - `curl -i https://www.example.org`
    - `curl -v https://www.example.org` でも見れる
- PowerShell
    - `Invoke-WebRequest`の戻り値がオブジェクトなので`Headers`メンバを参照する

        ```powershell
        $resp = Invoke-WebRequest https://www.example.org
        $resp.Headers
        ```

    - リクエストヘッダはないかも

# エラー応答の確認・ハンドリング

- curl
    - `-f`(`--fail`)を使う

        ```
        % curl -sSf -O https://ftp.gnu.org/gnu/wget/wget-1.19.5.tar.g 
        curl: (22) The requested URL returned error: 404 Not Found
        % echo $?
        22
        ```

    - `-f`無しだとHTTP上のエラーが発生してもcurlコマンド自体は正しく動作しているということで戻り値0になってしまう

- PowerShell
    - HTTPエラーが発生すると例外がthrowされる
    - `catch`していなくても、直近の例外は`$error[0]`にセットされる

        ```powershell
        PS > Invoke-WebRequest http://ftp.gnu.org/gnu/wget/wget-1.19.5.tar.g
        Invoke-WebRequest : Not Found
        The requested URL /gnu/wget/wget-1.19.5.tar.g was not found on this server.
        Apache/2.4.7 (Trisquel_GNU/Linux) Server at ftp.gnu.org Port 80
        発生場所 行:1 文字:1
        + Invoke-WebRequest http://ftp.gnu.org/gnu/wget/wget-1.19.5.tar.g
        + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest]、WebException
            + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
        PS> $error[0].Exception.Message
        リモート サーバーがエラーを返しました: (404) 見つかりません
        PS> $error[0].Exception.Response.StatusCode.value__
        404
        PS> $error[0].Exception.Response.StatusDescription
        Not Found
        ```

    - サーバの応答によってはレスポンスを参照できる

        ```
        PS > Invoke-WebRequest https://api.chatwork.com/v2/rooms
        Invoke-WebRequest : {"errors":["Invalid API Token"]}
        発生場所 行:1 文字:1
        + Invoke-WebRequest https://api.chatwork.com/v2/rooms
        + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest]、WebException
            + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
        PS > $error[0].ErrorDetails.Message
        {"errors":["Invalid API Token"]}
        PS > $error[0].Exception.Response.StatusCode.value__
        401
        ```


# 任意のHTTPリクエストヘッダを付加

- curl
    - `-H "HeaderKey: HeaderValue"`を付加
    - 複数ある場合は`-H`を複数回付加する
    - `curl -H "Authorization: Bearer foobar" -H "X-Header: hogehoge" http://www.example.org`
- PowerShell
    - 連想配列にしたヘッダ情報を引数に`-Headers $hashTable`を付加
    - `Invoke-WebRequest http://www.example.org -Headers @{Authorization: "Bearer foobar"; X-Header: "hogehoge"}`


# POSTする

## key=valueなパラメタでPOST

- curl
    - `-d`(`--data`)を使う
        - `curl -XPOST -d 'key1=value1&key2=value2' http://www.example.org`
    - `-d`を複数回使っても良い
        - `curl -XPOST -d 'key1=value1' -d 'key2=value2' http://www.example.org`
    - `-d`(`--data`)の替わりに`--data-urlencode`を使えば、パラメタ文字列のURLエンコードをcurlがやってくれる
        - `curl -XPOST -d 'key1=value1' --data-urlencode 'key2=/e/' http://www.example.or`
            - サーバには`key1=value1&key2=%2Fe%2F`が送られる
            - `--data-urlencode 'key1=value1&key2=/e/'`と書くと、`&`もエンコードされて`key1=value1%26key2%3D%2Fe%2F`になるので注意
- PowerShell
    - `Invoke-WebRequest -Method Post -Body 'key1=value1&key2=value2' http://www.example.org`
    - 連想配列にしても可
        - `Invoke-WebRequest -Method Post -Body @{key1="value1"; key2="value2"} http://www.example.org`
    - 文字列を指定する場合はURLエンコードは自前で処理する必要があるが、連想配列の場合は勝手にやってくれる
        - `Invoke-WebRequest -Method Post -Body @{key1="value1"; key2="/e/"} http://www.example.org`
            - サーバへは`key1=value1&key2=%2Fe%2F`がPOSTされる

## JSONなどの任意のBody用文字列丸ごとPOST

前述のkey=valueの場合とやることは同じ

- curl
    - `curl -XPOST -d '{ "key": { "subkey":"value" } }' http://www.example.org`
- PowerShell
    - `Invoke-WebRequest -Method Post -Body '{ "album": { "title":"create by api" } }' http://www.example.org`


## 外部ファイルをBodyとして丸ごとPOST

バイナリファイルを送信する場合など。
ファイル指定できるのでそれでPOSTする。
以下、カレントディレクトリから見て`Pictures/photo.jpg`にPostしたいファイルがあるとする。

- curl
    - `curl -XPOST --data-binary @Pictures/photo.jpg http://www.example.org`
- PowerShell
    - `Invoke-WebRequest -Method Post -InFile Pictures\photo.jpg http://www.example.org`


# HTTPS

## 証明書の検証(エラー)を無視する

テスト環境などでちゃんとした証明書を使ってなかったりDNS設定がない場合など

- curl
    - `-k`オプションを使う
    - `curl -k https://192.168.0.11`
- PowerShell
    - オプションだけでは実現できなさげ
    - .NET内の検証処理をオーバーライドするコードを追加すれば通る
    - [PowershellのInvoke-RestMethodをhttpsに実施すると失敗する - suusan2号の戯れ](http://suzan2go.hatenablog.com/entry/2014/10/18/192246)を参照

        ```powershell
        add-type @"
            using System.Net;
            using System.Security.Cryptography.X509Certificates;
            public class TrustAllCertsPolicy : ICertificatePolicy {
                public bool CheckValidationResult(
                    ServicePoint srvPoint, X509Certificate certificate,
                    WebRequest request, int certificateProblem) {
                    return true;
                }
            }
        "@
        [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
        
        Invoke-WebRequest https://192.168.0.11
        ```



## 暗号化プロトコルを指定してTLS1.2でアクセス

冒頭の例の https://ftp.gnu.org とか、あと https://qiita.com/ とかも、TLS1.2対応しており、Win10標準のPowerShell 5.1だと(デフォルトでは)対応していない。

```powershell
PS C:\Users\zaki> Invoke-WebRequest https://ftp.gnu.org/
Invoke-WebRequest : 要求は中止されました: SSL/TLS のセキュリティで保護されているチャネルを作成できませんでした
発生場所 行:1 文字:1
+ Invoke-WebRequest https://ftp.gnu.org/
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest]、WebExce
    ption
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand

PS C:\Users\zaki> Invoke-WebRequest http://qiita.com/
Invoke-WebRequest : 接続が切断されました: 送信時に、予期しないエラーが発生しました。。
発生場所 行:1 文字:1
+ Invoke-WebRequest http://qiita.com/
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest]、WebExce
    ption
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
```

現在の暗号化プロトコルの対応状態を表示(SSL3とTLSしか使えない)

```powershell
PS C:\Users\zaki> [Net.ServicePointManager]::SecurityProtocol
Ssl3, Tls
```

ただし、設定で追加はできる。追加できるプロトコルはこの通り。

```powershell
PS C:\Users\zaki> [enum]::GetNames([Net.SecurityProtocolType])
SystemDefault
Ssl3
Tls
Tls11
Tls12
```

TLS1.2を追加するには「既存設定にTLS1.2の項目を追加」してやる

```
PS C:\Users\zaki> [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
```

これで`Invoke-WebRequest https://ftp.gnu.org/`や`Invoke-WebRequest http://qiita.com/`すればHTTPアクセスできる。

サイトの暗号化プロトコルの種類を確認するには、Chromeならデベロッパーツールで。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/256859/3c0eea48-6e31-b2fa-c0b5-8f957ce0d3ba.png)

参考: [Windows PowerShellとTLS 1.2 - しばたテックブログ](https://blog.shibata.tech/entry/2018/04/30/184744)

ちなみにGitHubはTLS1.2を加えてもアクセスできない(反応がない)。
ただしGitHubは`-UseBasicParsing`を付加して「HTTPアクセスして得られたレスポンスを __IEコンポーネントを使って__ DOM解析を行う」オプションをオフにすればOKだった。

```powershell
PS C:\Users\zaki> Invoke-WebRequest https://github.com/ -UseBasicParsing


StatusCode        : 200
StatusDescription : OK
Content           :
:
:
```

ただしこのオプションは、[PowerShell 6でdeprecated](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-webrequest?view=powershell-6)になっている。

curlの場合はcurl本体のバージョンやOpenSSLに依存する模様。
[超古いサーバーからhttpsにcurlする時 - yamaguchi.txt](http://yamaguchi-1024.hatenablog.com/entry/2017/04/30/114314)

オプションとしては次のようなものがある。(`curl -h`で確認できる)

- `--sslv2`
- `--sslv3`
- `--tlsv1.2`
- `--tlsv1.3`