diff --git a/src/FsHttp/Domain.fs b/src/FsHttp/Domain.fs index 56ab6503..aa0f8de1 100644 --- a/src/FsHttp/Domain.fs +++ b/src/FsHttp/Domain.fs @@ -2,6 +2,7 @@ module FsHttp.Domain open System +open System.Text type StatusCodeExpectation = { expected: System.Net.HttpStatusCode list @@ -80,19 +81,23 @@ type ContentData = | FormUrlEncodedContent of Map | FileContent of string +type ContentType = + { mediaType: string + encoding: Encoding option + } + type BodyContent = { contentData: ContentData headers: Map - contentType: string option + contentType: ContentType option } type MultipartContent = - { contentData: + { parts: {| name: string - contentType: string option + contentType: ContentType option content: ContentData |} list headers: Map - contentType: string } type RequestContent = @@ -160,12 +165,10 @@ and HeaderContext = } interface IToMultipartContext with member this.Transform() = - let boundary = Guid.NewGuid().ToString("N") { MultipartContext.header = this.header content = { - MultipartContent.contentData = [] + MultipartContent.parts = [] headers = Map.empty - contentType = $"multipart/form-data; boundary={boundary}" } currentPartContentType = None config = this.config diff --git a/src/FsHttp/Dsl.fs b/src/FsHttp/Dsl.fs index 8ee26cbc..7c4c1c9a 100644 --- a/src/FsHttp/Dsl.fs +++ b/src/FsHttp/Dsl.fs @@ -357,7 +357,7 @@ module Multipart = content = content |} { context with content = { context.content with - contentData = context.content.contentData @ [ newContentData ] } } + parts = context.content.parts @ [ newContentData ] } } /// The MIME type of the body of the request (used with POST and PUT requests) let contentType (contentType: string) (context: IToMultipartContext) = diff --git a/src/FsHttp/Helper.fs b/src/FsHttp/Helper.fs index e37a71e3..6f3f580d 100644 --- a/src/FsHttp/Helper.fs +++ b/src/FsHttp/Helper.fs @@ -187,20 +187,3 @@ module Stream = let saveFileTAsync fileName source = saveFileAsync fileName source |> Async.StartAsTask - -[] -module FsHttpUrlExtensions = - type FsHttpUrl with - member this.ToUriString() = - let uri = UriBuilder(this.address) - let queryParamsString = - this.additionalQueryParams - |> Seq.map (fun kvp -> $"{kvp.Key}={kvp.Value}") - |> String.concat "&" - uri.Query <- - match uri.Query, queryParamsString with - | "", "" -> "" - | s, "" -> s - | "", q -> $"?{q}" - | s, q -> $"{s}&{q}" - uri.ToString() diff --git a/src/FsHttp/Print.fs b/src/FsHttp/Print.fs index ff363d49..68b74233 100644 --- a/src/FsHttp/Print.fs +++ b/src/FsHttp/Print.fs @@ -9,6 +9,8 @@ open System.Text open FsHttp open FsHttp.Helper +// We try to get as close to the underlying representation (requestMessage) when printing! + let internal contentIndicator = "===content===" let private printHeaderCollection (headers: KeyValuePair seq) = @@ -23,34 +25,19 @@ let private printHeaderCollection (headers: KeyValuePair seq do sb.appendLine (sprintf "%-*s: %s" (maxHeaderKeyLength + 3) h.Key values) sb.ToString() -let private doPrintRequestOnly (httpVersion: string) (request: Request) (requestMessage: HttpRequestMessage) = +let private doPrintRequestOnly (request: Request) (requestMessage: HttpRequestMessage) = let sb = StringBuilder() let requestPrintHint = request.config.printHint.requestPrintMode do sb.appendSection "REQUEST" - do sb.appendLine $"{request.header.method} {request.header.url.ToUriString()} HTTP/{httpVersion}" + do sb.appendLine $"{requestMessage.Method.Method} {requestMessage.RequestUri} HTTP/{requestMessage.Version}" let printRequestHeaders () = - let contentHeaders,multipartHeaders = - if not (isNull requestMessage.Content) then - let a = requestMessage.Content.Headers |> Seq.toList - let b = - match requestMessage.Content with - | :? MultipartFormDataContent as m -> - // TODO: After having the request invoked, the dotnet multiparts - // have no headers anymore... - m - |> Seq.collect (fun part -> part.Headers) - |> Seq.toList - | _ -> [] - a,b - else - [],[] - sb.append <| - printHeaderCollection ( - (requestMessage.Headers |> Seq.toList) - @ contentHeaders - @ multipartHeaders) + let contentHeaders = + if not (requestMessage.Content = null) + then requestMessage.Content.Headers |> Seq.toList + else [] + sb.append <| printHeaderCollection ((requestMessage.Headers |> Seq.toList) @ contentHeaders) let printRequestBody () = let formatContentData contentData = @@ -70,19 +57,15 @@ let private doPrintRequestOnly (httpVersion: string) (request: Request) (request | FileContent fileName -> sprintf "::File (name = %s)" fileName - let multipartIndicator = - match request.content with - | Multi _ -> " :: Multipart" - | _ -> "" - sb.appendLine (contentIndicator + multipartIndicator) + sb.appendLine contentIndicator sb.appendLine <| match request.content with | Empty -> "" | Single bodyContent -> formatContentData bodyContent.contentData | Multi multipartContent -> [ - for contentData in multipartContent.contentData do - yield $"-------- {contentData.name}" + for contentData in multipartContent.part do + yield $"-- {contentData.name}" yield "Part content type: " + (match contentData.contentType with | Some v -> v | _ -> "") yield formatContentData contentData.content ] diff --git a/src/FsHttp/Request.fs b/src/FsHttp/Request.fs index d9db3791..8954008d 100644 --- a/src/FsHttp/Request.fs +++ b/src/FsHttp/Request.fs @@ -1,6 +1,7 @@ module FsHttp.Request open System +open System.IO open System.Net open System.Net.Http open System.Net.Http.Headers @@ -34,7 +35,11 @@ let toRequestAndMessage (request: IToRequest): Request * HttpRequestMessage = let header = request.header let requestMessage = new HttpRequestMessage(header.method, header.url.ToUriString()) do setRequestMessageProp requestMessage TimeoutPropertyName request.config.timeout - let buildDotnetContent (part: ContentData) (contentType: string option) (name: string option) = + let buildDotnetContent + (part: ContentData) + (contentType: ContentType option) + (name: string option) + = let dotnetContent = match part with | StringContent s -> @@ -45,11 +50,8 @@ let toRequestAndMessage (request: IToRequest): Request * HttpRequestMessage = | FormUrlEncodedContent data -> new FormUrlEncodedContent(data) :> HttpContent | FileContent path -> - let content = - let fs = System.IO.File.OpenRead path - new StreamContent(fs) - let contentDispoHeaderValue = - ContentDispositionHeaderValue("form-data") + let content = new StreamContent(File.OpenRead path) + let contentDispoHeaderValue = ContentDispositionHeaderValue "form-data" match name with | Some v -> contentDispoHeaderValue.Name <- v | None -> () @@ -57,8 +59,14 @@ let toRequestAndMessage (request: IToRequest): Request * HttpRequestMessage = contentDispoHeaderValue.FileName <- path content.Headers.ContentDisposition <- contentDispoHeaderValue content :> HttpContent - if contentType.IsSome then - dotnetContent.Headers.ContentType <- MediaTypeHeaderValue.Parse contentType.Value + match contentType with + | Some contentType -> + let mediaTypeHeaderValue = MediaTypeHeaderValue.Parse contentType.mediaType + match contentType.encoding with + | Some encoding -> mediaTypeHeaderValue.CharSet <- encoding.WebName + | _ -> () + dotnetContent.Headers.ContentType <- mediaTypeHeaderValue + | _ -> () dotnetContent let assignContentHeaders (target: HttpHeaders) (headers: Map) = for kvp in headers do @@ -72,7 +80,7 @@ let toRequestAndMessage (request: IToRequest): Request * HttpRequestMessage = dotnetBodyContent | Multi multipartContent -> let dotnetMultipartContent = - match multipartContent.contentData with + match multipartContent.part with | [] -> null | contentPart -> let dotnetPart = new MultipartFormDataContent()