diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 3bc28535..72a711f5 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -71,15 +71,18 @@ - #112: Allow to add (multiple) headers (by @Samuel-Dufour - thank you) v11.0.0 - - Breaking change: #121: Turning off debug logs in FSI (breaking change in signature / namespace) + - #121 (Breaking change): Turning off debug logs in FSI (breaking change in signature / namespace) - #124: Support Repeating Query Parameters (thanks @DaveJohnson8080) - - Breaking change: #106: Allow filename metadata with other "parts" (thanks @dawedawe) + - #106 (Breaking change): Allow filename metadata with other "parts" (thanks @dawedawe) - Breaking change: ContentTypeForPart custom operations should come after part definition - - Breaking changes + - #104 (Breaking change): Automatic GZip response decompression + - Other breaking changes: - Removed `ContentTypeWithEncoding` and used optional `charset` parameter in `ContentType` overloads. - Renamed `byteArray` to `binary` in Dsl, DslCE and CSharp. - Caution (!!): Renamed `stringPart` to `textPart` and changed argument order for `name` and `value` in Dsl and DslCE. - Restructured types in Domain + - `Helper` is a module instead of a namespace, and some things were moved. + - All transformers in config are a list of transformers instead of a single item. diff --git a/src/FsHttp/Domain.fs b/src/FsHttp/Domain.fs index 78ca4ec3..acced5e7 100644 --- a/src/FsHttp/Domain.fs +++ b/src/FsHttp/Domain.fs @@ -42,9 +42,9 @@ type HttpClientHandlerTransformer = type Config = { timeout: System.TimeSpan option printHint: PrintHint - httpMessageTransformer: (System.Net.Http.HttpRequestMessage -> System.Net.Http.HttpRequestMessage) - httpClientHandlerTransformer: HttpClientHandlerTransformer - httpClientTransformer: (System.Net.Http.HttpClient -> System.Net.Http.HttpClient) + httpMessageTransformers: list System.Net.Http.HttpRequestMessage> + httpClientHandlerTransformers: list + httpClientTransformers: list System.Net.Http.HttpClient> httpCompletionOption: System.Net.Http.HttpCompletionOption proxy: Proxy option certErrorStrategy: CertErrorStrategy diff --git a/src/FsHttp/DomainExtensions.fs b/src/FsHttp/DomainExtensions.fs index ee5bfe72..19c5b373 100644 --- a/src/FsHttp/DomainExtensions.fs +++ b/src/FsHttp/DomainExtensions.fs @@ -1,9 +1,28 @@ [] module FsHttp.DomainExtensions +open System open System.Net.Http.Headers open FsHttp +type FsHttpUrl with + member this.ToUriString() = + let uri = UriBuilder(this.address) + + let queryParamsString = + this.additionalQueryParams + |> Seq.map (fun (k, v) -> $"""{k}={Uri.EscapeDataString $"{v}"}""") + |> String.concat "&" + + uri.Query <- + match uri.Query, queryParamsString with + | "", "" -> "" + | s, "" -> s + | "", q -> $"?{q}" + | s, q -> $"{s}&{q}" + + uri.ToString() + type ContentType with member this.ToMediaHeaderValue() = let mhv = MediaTypeHeaderValue.Parse(this.value) diff --git a/src/FsHttp/Dsl.fs b/src/FsHttp/Dsl.fs index d3b82444..9617b0ab 100644 --- a/src/FsHttp/Dsl.fs +++ b/src/FsHttp/Dsl.fs @@ -459,14 +459,14 @@ module Config = httpClientFactory = Some clientFactory } - let inline transformHttpClient transformer config = { config with httpClientTransformer = transformer } + let inline transformHttpClient transformer config = + { config with httpClientTransformers = config.httpClientTransformers @ [transformer] } - let inline transformHttpRequestMessage transformer config = { config with httpMessageTransformer = transformer } + let inline transformHttpRequestMessage transformer config = + { config with httpMessageTransformers = config.httpMessageTransformers @ [transformer] } - let inline transformHttpClientHandler transformer config = { - config with - httpClientHandlerTransformer = transformer - } + let inline transformHttpClientHandler transformer config = + { config with httpClientHandlerTransformers = config.httpClientHandlerTransformers @ [transformer] } let inline proxy url config = { config with diff --git a/src/FsHttp/FsHttp.fsproj b/src/FsHttp/FsHttp.fsproj index 8b19cc23..99e9a4c9 100644 --- a/src/FsHttp/FsHttp.fsproj +++ b/src/FsHttp/FsHttp.fsproj @@ -18,8 +18,8 @@ - + diff --git a/src/FsHttp/GlobalConfig.fs b/src/FsHttp/GlobalConfig.fs index 2d383c36..6fb75a27 100644 --- a/src/FsHttp/GlobalConfig.fs +++ b/src/FsHttp/GlobalConfig.fs @@ -15,9 +15,9 @@ let mutable private mutableDefaults = { requestPrintMode = HeadersAndBody(defaultHeadersAndBodyPrintMode ()) responsePrintMode = HeadersAndBody(defaultHeadersAndBodyPrintMode ()) } - httpMessageTransformer = id - httpClientHandlerTransformer = id - httpClientTransformer = id + httpMessageTransformers = [] + httpClientHandlerTransformers = [] + httpClientTransformers = [] httpClientFactory = None httpCompletionOption = System.Net.Http.HttpCompletionOption.ResponseHeadersRead proxy = None diff --git a/src/FsHttp/Helper.fs b/src/FsHttp/Helper.fs index 24f5ee0a..52027aae 100644 --- a/src/FsHttp/Helper.fs +++ b/src/FsHttp/Helper.fs @@ -1,26 +1,26 @@ -namespace FsHttp.Helper +module FsHttp.Helper open System open System.IO open System.Text +open System.Net.Http.Headers open System.Runtime.InteropServices open FsHttp +[] module Encoding = let base64 = Encoding.GetEncoding("ISO-8859-1") -[] -module StringBuilderExtensions = - type StringBuilder with - member sb.append(s: string) = sb.Append s |> ignore - member sb.appendLine(s: string) = sb.AppendLine s |> ignore - member sb.newLine() = sb.appendLine "" +type StringBuilder with + member sb.append(s: string) = sb.Append s |> ignore + member sb.appendLine(s: string) = sb.AppendLine s |> ignore + member sb.newLine() = sb.appendLine "" - member sb.appendSection(s: string) = - sb.appendLine s + member sb.appendSection(s: string) = + sb.appendLine s - String([ 0 .. s.Length ] |> List.map (fun _ -> '-') |> List.toArray) - |> sb.appendLine + String([ 0 .. s.Length ] |> List.map (fun _ -> '-') |> List.toArray) + |> sb.appendLine [] module Map = @@ -56,62 +56,62 @@ module Url = let b = (norm url2).TrimStart(delTrim).TrimEnd(delTrim) a + sdel + b -[] -module Stream = - // TODO: Inefficient - type Utf8StringBufferingStream(baseStream: Stream, readBufferLimit: int option) = - inherit Stream() - let notSeekable () = failwith "Stream is not seekable." - let notWritable () = failwith "Stream is not writable." - let readBuffer = ResizeArray() - override _.Flush() = baseStream.Flush() - - override _.Read(buffer, offset, count) = - let readCount = baseStream.Read(buffer, offset, count) - - match readCount, readBufferLimit with - | 0, _ -> () - | readCount, None -> readBuffer.AddRange(buffer[offset .. readCount - 1]) - | readCount, Some limit -> - let remaining = limit - readBuffer.Count - let copyCount = min remaining readCount - - if copyCount > 0 then - readBuffer.AddRange(buffer[offset .. copyCount - 1]) - - readCount - - override _.Seek(_, _) = notSeekable () - override _.SetLength(_) = notWritable () - override _.Write(_, _, _) = notWritable () - override _.CanRead = true - override _.CanSeek = false - override _.CanWrite = false - override _.Length = baseStream.Length - - override _.Position - with get () = baseStream.Position - and set (_) = notSeekable () - - member _.GetUtf8String() = +// TODO: Inefficient +type Utf8StringBufferingStream(baseStream: Stream, readBufferLimit: int option) = + inherit Stream() + let notSeekable () = failwith "Stream is not seekable." + let notWritable () = failwith "Stream is not writable." + let readBuffer = ResizeArray() + override _.Flush() = baseStream.Flush() + + override _.Read(buffer, offset, count) = + let readCount = baseStream.Read(buffer, offset, count) + + match readCount, readBufferLimit with + | 0, _ -> () + | readCount, None -> readBuffer.AddRange(buffer[offset .. readCount - 1]) + | readCount, Some limit -> + let remaining = limit - readBuffer.Count + let copyCount = min remaining readCount + + if copyCount > 0 then + readBuffer.AddRange(buffer[offset .. copyCount - 1]) + + readCount + + override _.Seek(_, _) = notSeekable () + override _.SetLength(_) = notWritable () + override _.Write(_, _, _) = notWritable () + override _.CanRead = true + override _.CanSeek = false + override _.CanWrite = false + override _.Length = baseStream.Length + + override _.Position + with get () = baseStream.Position + and set (_) = notSeekable () + + member _.GetUtf8String() = #if NETSTANDARD2_0 || NETSTANDARD2_1 - let buffer = readBuffer |> Seq.toArray + let buffer = readBuffer |> Seq.toArray #else - let buffer = CollectionsMarshal.AsSpan(readBuffer) + let buffer = CollectionsMarshal.AsSpan(readBuffer) #endif - let s = Encoding.UTF8.GetString(buffer).AsSpan() + let s = Encoding.UTF8.GetString(buffer).AsSpan() - if s.Length = 0 then - s.ToString() - else - let s = - if s[s.Length - 1] |> Char.IsHighSurrogate then - s.Slice(0, s.Length - 1) - else - s + if s.Length = 0 then + s.ToString() + else + let s = + if s[s.Length - 1] |> Char.IsHighSurrogate then + s.Slice(0, s.Length - 1) + else + s - s.ToString() + s.ToString() +[] +module Stream = let readUtf8StringAsync maxUtf16CharCount (stream: Stream) = let sr = new StreamReader(stream, Encoding.UTF8) let sb = StringBuilder() @@ -214,23 +214,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 (k, v) -> $"""{k}={Uri.EscapeDataString $"{v}"}""") - |> String.concat "&" - - uri.Query <- - match uri.Query, queryParamsString with - | "", "" -> "" - | s, "" -> s - | "", q -> $"?{q}" - | s, q -> $"{s}&{q}" - - uri.ToString() diff --git a/src/FsHttp/Request.fs b/src/FsHttp/Request.fs index acac272c..b861e0b7 100644 --- a/src/FsHttp/Request.fs +++ b/src/FsHttp/Request.fs @@ -152,7 +152,9 @@ let private getHttpClient config = | Default -> false | AlwaysAccept -> true - let handler = config.httpClientHandlerTransformer (getSslHandler ignoreSslIssues) + let handler = + let initHandler = getSslHandler ignoreSslIssues + config.httpClientHandlerTransformers |> List.fold (fun c n -> n c) initHandler match config.proxy with | Some proxy -> @@ -176,7 +178,7 @@ let toAsync (context: IToRequest) = async { let request, requestMessage = toRequestAndMessage context do Fsi.logfn $"Sending request {request.header.method} {request.header.url.ToUriString()} ..." - use finalRequestMessage = request.config.httpMessageTransformer requestMessage + use finalRequestMessage = request.config.httpMessageTransformers |> List.fold (fun c n -> n c) requestMessage let! ctok = Async.CancellationToken let client = getHttpClient request.config @@ -186,7 +188,7 @@ let toAsync (context: IToRequest) = let cookies = cookies |> List.map string |> String.concat "; " do finalRequestMessage.Headers.Add("Cookie", cookies) - let finalClient = request.config.httpClientTransformer client + let finalClient = request.config.httpClientTransformers |> List.fold (fun c n -> n c) client let! response = finalClient.SendAsync(finalRequestMessage, request.config.httpCompletionOption, ctok) diff --git a/src/FsHttp/Response.fs b/src/FsHttp/Response.fs index 65daadb7..796dc53f 100644 --- a/src/FsHttp/Response.fs +++ b/src/FsHttp/Response.fs @@ -33,7 +33,7 @@ let toStream response = (toStreamTAsync response).Result let parseAsync parserName parse response = async { use! contentStream = toStreamAsync response - use bufferingStream = new Stream.Utf8StringBufferingStream(contentStream, None) + use bufferingStream = new Utf8StringBufferingStream(contentStream, None) try let! ct = Async.CancellationToken diff --git a/src/Tests/Helper.fs b/src/Tests/Helper.fs index 5f398dd2..09363ed8 100644 --- a/src/Tests/Helper.fs +++ b/src/Tests/Helper.fs @@ -48,7 +48,7 @@ let private testUtf8StringBufferingStream limit = let text = "abcdefghijklmnop" let bs = - new Stream.Utf8StringBufferingStream(new MemoryStream(Encoding.UTF8.GetBytes(text)), limit) + new Utf8StringBufferingStream(new MemoryStream(Encoding.UTF8.GetBytes(text)), limit) let sr = new StreamReader(bs) do sr.ReadToEnd() |> ignore