F# Data


F# Data: HTTP ユーティリティ

.NETライブラリにはHTTP Webリクエストを作成して送信するための 強力なAPIが用意されています。 具体的には単純な WebClient 型(MSDN を参照)や、 より柔軟な機能を持った HttpWebRequest (MSDN を参照)があります。 しかしこれらはいずれも、HTTP POSTデータや追加のヘッダなど、 特定の引数を指定した単純なHTTPリクエストを手軽に実行することには向いていません。

F# Dataライブラリにはオーバーロードを持った4つのメソッドを含む、 単純な Http 型があります。 RequestStringAsyncRequestString メソッドは単純なリクエストを作成して、 同期的あるいは非同期的にリクエストを送信できます。 また Request とその非同期バージョン AsyncRequest を使うと バイナリファイルを送信したり、ステータスコードや応答URL、 受信ヘッダやクッキーなど、レスポンスの詳細情報を知ることができます。

この型を使うには、まず(F# Interactiveの場合は) #r あるいはプロジェクトで 参照の追加を行ってライブラリを参照する必要があります。 この型は FSharp.Net 名前空間にあります:

1: 
2: 
#r "../../../../bin/FSharp.Data.dll"
open FSharp.Data

単純なリクエストの送信

特定のWebページをダウンロードするような単純なHTTP (GET) リクエストを送信するには、 Http.RequestString あるいは Http.AsyncRequestString にたった1つの引数を 指定するだけです:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
// Webサイトのコンテンツをダウンロード
Http.RequestString("http://tomasp.net")

// Webサイトから非同期的にダウンロード
async { let! html = Http.AsyncRequestString("http://tomasp.net")
        printfn "%d" html.Length }
|> Async.Start

AsyncRequestStringRequestString と全く同じ方法で使うことができるため、 以降では RequestString だけを使います。

クエリ引数とヘッダ

クエリ引数は引数を含んだURLを用意するか、 query という省略可能な引数を使って指定できます。 以下では明示的にGETメソッドであることを指定していますが、 省略した場合には自動的にGETになります:

1: 
Http.RequestString("http://httpbin.org/get", query=["test", "foo"], httpMethod="Get")

同じように、省略可能な引数 headers を使うと追加のヘッダを指定できます。 このコレクションには独自のヘッダを追加できますが、 Acceptヘッダのような標準的なヘッダも含まれています ( HttpWebRequest の場合には特定のプロパティに設定する必要があります)。

以下では The Movie Database APIを使って 「batman」という単語を検索しています。 サンプルを実行するためには登録をしてAPIキーを取得する必要があります:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
// http://www.themoviedb.org 用のAPIキー
let apiKey = "<登録してキーを取得してください>"

// HTTP Webリクエストを実行
Http.RequestString
  ( "http://api.themoviedb.org/3/search/movie", httpMethod = "GET",
    query   = [ "api_key", apiKey; "query", "batman" ],
    headers = [ "Accept", "application/json" ])

このライブラリでは(先の例のように)単純かつチェックのない文字列ベースのAPIがサポートされていますが、 スペルミスを防ぐことができるよう、あらかじめ定義されたヘッダ名を使うこともできます。 名前付きヘッダは HttpRequestHeaders (および HttpResponseHeaders) モジュールで定義されているため、 HttpRequestHeaders.Accept のようにフルネームを指定するか、 以下の例のようにモジュールをオープンしておいてから Accept のような短い名前で指定することになります。 同様に、 HttpContentTypes 列挙体には既知のコンテンツタイプ名が定義されています:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
open FSharp.Data.HttpRequestHeaders

// HTTP Webリクエストを実行
Http.RequestString
  ( "http://api.themoviedb.org/3/search/movie",
    query   = [ "api_key", apiKey; "query", "batman" ],
    headers = [ Accept HttpContentTypes.Json ])

特別な情報の取得

先のコードスニペットでは、正しいAPIキーを指定しなかった場合には(401)認証エラーが返され、 例外が発生することに注意してください。 ただし WebRequest を直接使用した場合とは異なり、例外メッセージには返されたコンテンツの情報も含まれるため、 サーバーが特別な情報を返すような場合であってもF# Interactiveで簡単にデバッグできます。

また silentHttpErrors 引数を設定することによって例外を無効化することもできます:

1: 
Http.RequestString("http://api.themoviedb.org/3/search/movie", silentHttpErrors = true)

この結果は以下のようになります:

"{"status_code":7,"status_message":"Invalid API key: You must be granted a valid key."}"

この場合にはHTTPステータスコードを確認すればよいため、実際にレスポンスとして返されたデータと エラーメッセージとを混同することもないでしょう。 ステータスコードやレスポンスヘッダー、返されたクッキー、レスポンスURL (リダイレクトされた場合にはリクエストを投げたURLとは別の値が返されることがあります)など、 レスポンスの詳細を確認したい場合には RequestString の代わりに Request メソッドを使用します:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
let response = Http.Request("http://api.themoviedb.org/3/search/movie", silentHttpErrors = true)

// レスポンスの詳細を確認する
response.Headers
response.Cookies
response.ResponseUrl
response.StatusCode

リクエストデータの送信

HTTP POSTデータを含んだPOSTリクエストを作成したい場合は、 オプション引数 body に追加データを指定するだけです。 この引数は3つのケースを持った判別共用体 HttpRequestBody 型です:

  • TextRequest はリクエストの本体で文字列を送信するために使用します
  • BinaryUpload はリクエストにバイナリデータを含めて送信する場合に使用します
  • FormValues は特定のフォームの値を名前と値のペアとして 送信するために使用します

bodyを指定した場合、引数 httpMethod には自動的に Post が設定されるようになるため、 明示的に指定する必要はありません。

以下ではリクエストの詳細を返すサービス httpbin.org を使っています:

1: 
Http.RequestString("http://httpbin.org/post", body = FormValues ["test", "foo"])

デフォルトでは Content-Type ヘッダには HttpRequestBody に指定した値に応じて text/plain application/x-www-form-urlencoded application/octet-stream のいずれかが設定されます。 ただしオプション引数 headers を使ってヘッダのリストに content-type を 追加することでこの動作を変更できます:

1: 
2: 
3: 
4: 
Http.RequestString
  ( "http://httpbin.org/post", 
    headers = [ ContentType HttpContentTypes.Json ],
    body = TextRequest """ {"test": 42} """)

リクエスト間でクッキーを管理する

リクエスト間でクッキーを管理したい場合には、 引数 cookieContainer を指定します。 以下では HttpRequest クラスに関するMSDNドキュメントをリクエストしています。 そうするとF#ではなくC#のコードスニペットが表示されます:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
// 特定のクラスに関するドキュメント用のURLを用意する
let msdnUrl className = 
  let root = "http://msdn.microsoft.com"
  sprintf "%s/en-gb/library/%s.aspx" root className

// ページを取得してF#コードを検索する
let docInCSharp = Http.RequestString(msdnUrl "system.web.httprequest")
docInCSharp.Contains "<a>F#</a>"

別のMSDNのページに移動してF#のコード例をクリックしてから HttpRequest クラスのドキュメントに戻ってくると、 同じ cookieContainer を保持し続けている限りはF#のコードが 表示されるようになります:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
open System.Net
let cc = CookieContainer()

// 言語を切り替えるためのリクエストを送信
Http.RequestString
  ( msdnUrl "system.datetime", 
    query = ["cs-save-lang", "1"; "cs-lang","fsharp"], 
    cookieContainer = cc) |> ignore

// 再度ドキュメントをリクエストしてF#のコードを検索
let docInFSharp = 
  Http.RequestString
    ( msdnUrl "system.web.httprequest", 
      cookieContainer = cc )
docInFSharp.Contains "<a>F#</a>"

バイナリデータの送信

RequestString メソッドでは常に string としてレスポンスが返されます。 しかし Request メソッドの場合にはレスポンスの content-type ヘッダに応じて HttpResponseBody.Text または HttpResponseBody.Binary が返されます:

1: 
2: 
3: 
4: 
5: 
6: 
let logoUrl = "https://raw.github.com/fsharp/FSharp.Data/master/misc/logo.png"
match Http.Request(logoUrl).Body with
| Text text -> 
    printfn "Got text content: %s" text
| Binary bytes -> 
    printfn "Got %d bytes of binary content" bytes.Length

HTTPリクエストのカスタマイズ

ライブラリに元々備えられていないような機能を使用したい場合には、 customizeHttpRequest 引数を設定することになります。 この引数には HttpWebRequest を変更するための関数を指定します。

たとえばリクエストにクライアント証明書を追加したいとします。 そのためにはまず System.Security.Cryptography 以下の X509Certificates 名前空間をオープンして、 X509ClientCertificate2 の値を用意し、 この値をリクエストの ClientCertificates リストに追加します。

証明書が myCertificate.pfx に格納されているとすると、 以下のようなコードになります:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
open System.Security.Cryptography.X509Certificates

// ローカルファイルから証明書を読み取り
let clientCert = 
  new X509Certificate2(".\myCertificate.pfx", "password")

// 証明書付きでリクエストを送信
Http.Request
  ( "http://yourprotectedresouce.com/data",
    customizeHttpRequest = fun req -> req.ClientCertificates.Add(clientCert) |> ignore; req)

関連する記事

namespace FSharp
namespace FSharp.Data
type Http =
  private new : unit -> Http
  static member private AppendQueryToUrl : url:string * query:(string * string) list -> string
  static member AsyncRequest : url:string * ?query:(string * string) list * ?headers:(string * string) list * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:(string * string) list * ?cookieContainer:CookieContainer * ?silentHttpErrors:bool * ?responseEncodingOverride:string * ?customizeHttpRequest:(HttpWebRequest -> HttpWebRequest) -> Async<HttpResponse>
  static member AsyncRequestStream : url:string * ?query:(string * string) list * ?headers:(string * string) list * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:(string * string) list * ?cookieContainer:CookieContainer * ?silentHttpErrors:bool * ?customizeHttpRequest:(HttpWebRequest -> HttpWebRequest) -> Async<HttpResponseWithStream>
  static member AsyncRequestString : url:string * ?query:(string * string) list * ?headers:(string * string) list * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:(string * string) list * ?cookieContainer:CookieContainer * ?silentHttpErrors:bool * ?responseEncodingOverride:string * ?customizeHttpRequest:(HttpWebRequest -> HttpWebRequest) -> Async<string>
  static member private InnerRequest : url:string * toHttpResponse:(string -> int -> string -> string -> 'a0 option -> Map<string,string> -> Map<string,string> -> MemoryStream -> Async<'a1>) * ?query:(string * string) list * ?headers:(string * string) list * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:(string * string) list * ?cookieContainer:CookieContainer * ?silentHttpErrors:bool * ?responseEncodingOverride:'a0 * ?customizeHttpRequest:(HttpWebRequest -> HttpWebRequest) -> Async<'a1>
  static member Request : url:string * ?query:(string * string) list * ?headers:(string * string) list * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:(string * string) list * ?cookieContainer:CookieContainer * ?silentHttpErrors:bool * ?responseEncodingOverride:string * ?customizeHttpRequest:(HttpWebRequest -> HttpWebRequest) -> HttpResponse
  static member RequestStream : url:string * ?query:(string * string) list * ?headers:(string * string) list * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:(string * string) list * ?cookieContainer:CookieContainer * ?silentHttpErrors:bool * ?customizeHttpRequest:(HttpWebRequest -> HttpWebRequest) -> HttpResponseWithStream
  static member RequestString : url:string * ?query:(string * string) list * ?headers:(string * string) list * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:(string * string) list * ?cookieContainer:CookieContainer * ?silentHttpErrors:bool * ?responseEncodingOverride:string * ?customizeHttpRequest:(HttpWebRequest -> HttpWebRequest) -> string

Full name: FSharp.Data.Http
static member Http.RequestString : url:string * ?query:(string * string) list * ?headers:(string * string) list * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:(string * string) list * ?cookieContainer:System.Net.CookieContainer * ?silentHttpErrors:bool * ?responseEncodingOverride:string * ?customizeHttpRequest:(System.Net.HttpWebRequest -> System.Net.HttpWebRequest) -> string
val async : AsyncBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
val html : string
static member Http.AsyncRequestString : url:string * ?query:(string * string) list * ?headers:(string * string) list * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:(string * string) list * ?cookieContainer:System.Net.CookieContainer * ?silentHttpErrors:bool * ?responseEncodingOverride:string * ?customizeHttpRequest:(System.Net.HttpWebRequest -> System.Net.HttpWebRequest) -> Async<string>
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
property System.String.Length: int
Multiple items
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken

Full name: Microsoft.FSharp.Control.Async

--------------------
type Async<'T>

Full name: Microsoft.FSharp.Control.Async<_>
static member Async.Start : computation:Async<unit> * ?cancellationToken:System.Threading.CancellationToken -> unit
val query : Linq.QueryBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.query
val apiKey : string

Full name: Http.apiKey
module HttpRequestHeaders

from FSharp.Data
val Accept : contentType:string -> string * string

Full name: FSharp.Data.HttpRequestHeaders.Accept
module HttpContentTypes

from FSharp.Data
val Json : string

Full name: FSharp.Data.HttpContentTypes.Json
val response : HttpResponse

Full name: Http.response
static member Http.Request : url:string * ?query:(string * string) list * ?headers:(string * string) list * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:(string * string) list * ?cookieContainer:System.Net.CookieContainer * ?silentHttpErrors:bool * ?responseEncodingOverride:string * ?customizeHttpRequest:(System.Net.HttpWebRequest -> System.Net.HttpWebRequest) -> HttpResponse
HttpResponse.Headers: Map<string,string>
HttpResponse.Cookies: Map<string,string>
HttpResponse.ResponseUrl: string
HttpResponse.StatusCode: int
union case HttpRequestBody.FormValues: seq<string * string> -> HttpRequestBody
val ContentType : contentType:string -> string * string

Full name: FSharp.Data.HttpRequestHeaders.ContentType
union case HttpRequestBody.TextRequest: string -> HttpRequestBody
val msdnUrl : className:string -> string

Full name: Http.msdnUrl
val className : string
val root : string
val sprintf : format:Printf.StringFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
val docInCSharp : string

Full name: Http.docInCSharp
System.String.Contains(value: string) : bool
namespace System
namespace System.Net
val cc : CookieContainer

Full name: Http.cc
Multiple items
type CookieContainer =
  new : unit -> CookieContainer + 2 overloads
  member Add : cookie:Cookie -> unit + 3 overloads
  member Capacity : int with get, set
  member Count : int
  member GetCookieHeader : uri:Uri -> string
  member GetCookies : uri:Uri -> CookieCollection
  member MaxCookieSize : int with get, set
  member PerDomainCapacity : int with get, set
  member SetCookies : uri:Uri * cookieHeader:string -> unit
  static val DefaultCookieLimit : int
  ...

Full name: System.Net.CookieContainer

--------------------
CookieContainer() : unit
CookieContainer(capacity: int) : unit
CookieContainer(capacity: int, perDomainCapacity: int, maxCookieSize: int) : unit
static member Http.RequestString : url:string * ?query:(string * string) list * ?headers:(string * string) list * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:(string * string) list * ?cookieContainer:CookieContainer * ?silentHttpErrors:bool * ?responseEncodingOverride:string * ?customizeHttpRequest:(HttpWebRequest -> HttpWebRequest) -> string
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
val docInFSharp : string

Full name: Http.docInFSharp
val logoUrl : string

Full name: Http.logoUrl
static member Http.Request : url:string * ?query:(string * string) list * ?headers:(string * string) list * ?httpMethod:string * ?body:HttpRequestBody * ?cookies:(string * string) list * ?cookieContainer:CookieContainer * ?silentHttpErrors:bool * ?responseEncodingOverride:string * ?customizeHttpRequest:(HttpWebRequest -> HttpWebRequest) -> HttpResponse
union case HttpResponseBody.Text: string -> HttpResponseBody
val text : string
union case HttpResponseBody.Binary: byte [] -> HttpResponseBody
val bytes : byte []
property System.Array.Length: int
namespace System.Security
namespace System.Security.Cryptography
namespace System.Security.Cryptography.X509Certificates
val clientCert : X509Certificate2

Full name: Http.clientCert
Multiple items
type X509Certificate2 =
  inherit X509Certificate
  new : unit -> X509Certificate2 + 12 overloads
  member Archived : bool with get, set
  member Extensions : X509ExtensionCollection
  member FriendlyName : string with get, set
  member GetNameInfo : nameType:X509NameType * forIssuer:bool -> string
  member HasPrivateKey : bool
  member Import : rawData:byte[] -> unit + 5 overloads
  member IssuerName : X500DistinguishedName
  member NotAfter : DateTime
  member NotBefore : DateTime
  ...

Full name: System.Security.Cryptography.X509Certificates.X509Certificate2

--------------------
X509Certificate2() : unit
   (+0 other overloads)
X509Certificate2(rawData: byte []) : unit
   (+0 other overloads)
X509Certificate2(fileName: string) : unit
   (+0 other overloads)
X509Certificate2(handle: nativeint) : unit
   (+0 other overloads)
X509Certificate2(certificate: X509Certificate) : unit
   (+0 other overloads)
X509Certificate2(rawData: byte [], password: string) : unit
   (+0 other overloads)
X509Certificate2(rawData: byte [], password: System.Security.SecureString) : unit
   (+0 other overloads)
X509Certificate2(fileName: string, password: string) : unit
   (+0 other overloads)
X509Certificate2(fileName: string, password: System.Security.SecureString) : unit
   (+0 other overloads)
X509Certificate2(rawData: byte [], password: string, keyStorageFlags: X509KeyStorageFlags) : unit
   (+0 other overloads)
val req : HttpWebRequest
property HttpWebRequest.ClientCertificates: X509CertificateCollection
X509CertificateCollection.Add(value: X509Certificate) : int
Fork me on GitHub