Skip to content

Commit

Permalink
avoid some allocations plust some code clean up
Browse files Browse the repository at this point in the history
  • Loading branch information
ademar committed Sep 20, 2024
1 parent 445fec8 commit d08f7a4
Show file tree
Hide file tree
Showing 24 changed files with 79 additions and 179 deletions.
2 changes: 1 addition & 1 deletion examples/Example/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ let app =
x.files
|> Seq.map (fun y -> sprintf "(%s, %s, %s)" y.fileName y.mimeType y.tempFilePath)
|> String.concat "<br/>"
OK (sprintf "Upload successful.<br>POST data: %A<br>Uploaded files (%d): %s" x.multiPartFields (List.length x.files) files))
OK (sprintf "Upload successful.<br>POST data: %A<br>Uploaded files (%d): %s" x.multiPartFields x.files.Count files))
POST >=> request (fun x -> OK (sprintf "POST data: %s" (System.Text.Encoding.ASCII.GetString x.rawForm)))
GET
>=> path "/custom_header"
Expand Down
5 changes: 0 additions & 5 deletions examples/WebSocket/Program.fs
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
open Suave
open Suave.Http
open Suave.Operators
open Suave.Filters
open Suave.Successful
open Suave.Files
open Suave.RequestErrors
open Suave.Logging
open Suave.Utils

open System
open System.Net
open System.Text

open Suave.Sockets
Expand Down
5 changes: 3 additions & 2 deletions src/Suave.Tests/HttpRequestHeaders.fs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
module Suave.Tests.HttpRequestHeaders
module Suave.Tests.HttpRequestHeaders

open Expecto
open System.Collections.Generic

open Suave

[<Tests>]
let headers (_:SuaveConfig) =
testList "Request header letter case" [
testCase "compare header names case-insensitively" <| fun _ ->
let req = { HttpRequest.empty with headers = ["x-suave-customheader", "value"] }
let req = { HttpRequest.empty with headers = List<_>(["x-suave-customheader", "value"]) }
let actual = req.header "X-Suave-CustomHeader"
Expect.equal actual (Choice1Of2 "value") "results in Choice1Of2"
]
4 changes: 2 additions & 2 deletions src/Suave.Tests/HttpVerbs.fs
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ let posts cfg =
request (fun x -> let q = x.fieldData name in OK (get q))

let getFileContent _ =
request (fun x -> let q = List.head x.files in OK (IO.File.ReadAllText q.tempFilePath))
request (fun x -> let q = x.files[0] in OK (IO.File.ReadAllText q.tempFilePath))

let getFileName _ =
request (fun x -> let q = List.head x.files in OK q.fileName)
request (fun x -> let q = x.files[0] in OK q.fileName)

let assertion = "eyJhbGciOiJSUzI1NiJ9.eyJwdWJsaWMta2V5Ijp7ImFsZ29yaXRobSI6IkR"+
"TIiwieSI6Ijc1MDMyNGRmYzQwNGI0OGQ3ZDg0MDdlOTI0NWMxNGVkZmVlZTY"+
Expand Down
3 changes: 2 additions & 1 deletion src/Suave.Tests/Model.fs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module Suave.Tests.Model

open System.Collections.Generic
open System.Text
open System.Net.Http

Expand All @@ -25,7 +26,7 @@ let modelTests cfg =
testList "Suave.Model" [
testCase "header" <| fun _ ->
let expected = "application/vnd.lolcatz; version=1.0"
let request = { HttpRequest.empty with headers = [ "Content-Type", expected ] }
let request = { HttpRequest.empty with headers = List<_>([ "Content-Type", expected ]) }

let subject = Binding.header "Content-Type" Choice1Of2 request
Assert.Equal("should have bound Content-Type",
Expand Down
10 changes: 4 additions & 6 deletions src/Suave.Tests/Parsing.fs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module Suave.Tests.Parsing
module Suave.Tests.Parsing

open Expecto
open System
Expand Down Expand Up @@ -84,8 +84,6 @@ let parsingMultipart cfg =

open System.Net
open System.Net.Sockets
open Suave.Logging
open Suave.Sockets

[<Tests>]
let parsingMultipart2 cfg =
Expand All @@ -99,22 +97,22 @@ let parsingMultipart2 cfg =
[ POST
>=> choose [
path "/filecount" >=> warbler (fun ctx ->
OK (string ctx.request.files.Length))
OK (string ctx.request.files.Count))

path "/filenames"
>=> Writers.setMimeType "application/json"
>=> warbler (fun ctx ->
//printfn "inside suave"
ctx.request.files
|> List.map (fun f ->
|> Seq.map (fun f ->
"\"" + f.fileName + "\"")
|> String.concat ","
|> fun files -> "[" + files + "]"
|> OK)

path "/msgid"
>=> request (fun r ->
match r.multiPartFields |> List.tryFind (fst >> (=) "messageId") with
match r.multiPartFields |> Seq.tryFind (fst >> (=) "messageId") with
| Some (_, yep) -> OK yep
| None -> NOT_FOUND "Nope... Not found"
)
Expand Down
1 change: 0 additions & 1 deletion src/Suave.Tests/Suave.Tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
<Compile Include="Sscanf.fs" />
<Compile Include="Json.fs" />
<Compile Include="Auth.fs" />
<Compile Include="TraceHeader.fs" />
<Compile Include="Cookie.fs" />
<Compile Include="Types.fs" />
<Compile Include="WebSocket.fs" />
Expand Down
7 changes: 0 additions & 7 deletions src/Suave.Tests/TestUtilities.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,8 @@ module Suave.Tests.TestUtilities

open System
open System.IO
open System.Threading
open System.Net
open System.Net.Http
open System.Net.Http.Headers
open System.Reflection
open System.Text
open Suave
open Suave.Web
open Suave.Logging
open Expecto
open FsCheck

Expand Down
10 changes: 4 additions & 6 deletions src/Suave.Tests/Types.fs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
module Suave.Tests.Types
module Suave.Tests.Types

open System
open System.Collections.Generic
open System.Net
open Expecto
open Suave.Sockets
open Suave
open System.Net.Http
open Suave.Testing

[<Tests>]
let socketBinding (_ : SuaveConfig) =
Expand Down Expand Up @@ -124,9 +122,9 @@ let httpReqIndexedPropertyMultiPartFieldsData (_ : SuaveConfig) =

testList "Http Request Index Property for retrieving multi part fields data" [
testCase "get multi part fields value for the given key" <| fun _ ->
let req = createReq [("name", "bob")]
let req = createReq (List<_>([("name", "bob")]))
Expect.equal req.["name"] (Some "bob") "multi part fields data "
testCase "get multi part fields value for a key which is absent" <| fun _ ->
let req = createReq [("name", "bob")]
let req = createReq (List<_>([("name", "bob")]))
Expect.equal req.["age"] None "multi part fields data "
]
36 changes: 4 additions & 32 deletions src/Suave.Tests/Web.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,12 @@ open Expecto

open Suave
open Suave.Operators
open Suave.Logging
open Suave.Testing
open Suave.Sockets
open System
open System.Collections.Generic
open System.Net

let private (=>) a b = a, b

[<Tests>]
let parsing_tests (_: SuaveConfig) =
testList "when parsing headers for tracing" [
// https://github.com/twitter/zipkin/blob/master/doc/collector-api.md#http
// Parsing these headers are as good as anything, we don't have to use ZipKin
// but it's the only reference implementation I've found for how to represent spans
// and traces over http, except dapper.
//
// In a similar vein to how 'username' and 'password' are hard-coded
// in the request record, let's have these there until we need to branch out with
// more variants.
testCase "parsing full span/trace headers" <| fun _ ->
let headers =
[ "x-b3-spanid" => "1234567"
"x-b3-traceid" => "7654321"
"x-b3-parentspanid" => "1818181" ]
// these are not supported:
//"X-B3-Flags" => ""
//"X-B3-Sampled" => "true" ]

// the server generates a new one with the client's as the parent?
// is the semantics that client sends its SpanId and that
let expected = TraceHeader.create (Some 7654321UL) (Some 1234567UL)
Expect.equal (TraceHeader.parseTraceHeaders headers).traceId expected.traceId "should parse trace id"
Expect.equal (TraceHeader.parseTraceHeaders headers).reqParentId expected.reqParentId "should parse span id to parent span id"
]
open Suave.Sockets

[<Tests>]
let transferEncodingChunkedTests (cfg : SuaveConfig) =
Expand Down Expand Up @@ -109,8 +81,8 @@ let keepAliveTests (cfg : SuaveConfig) =
let genKeepAliveTest httpVersion connectionHeader shouldAddKeepAlive =
let connectionHeaderDesc, reqHeaders =
match connectionHeader with
| Some v -> sprintf "a 'Connection: %s' header" v, [("connection", v)]
| None -> "no Connection header", []
| Some v -> sprintf "a 'Connection: %s' header" v, List<_>([("connection", v)])
| None -> "no Connection header", List<_>()
testCase (sprintf "for an %s request with %s" httpVersion connectionHeaderDesc) <| fun _ ->
let reqContext = { HttpContext.empty with request = { HttpContext.empty.request with httpVersion = httpVersion; headers = reqHeaders } }
let message, expected =
Expand Down
7 changes: 2 additions & 5 deletions src/Suave.Tests/regressions/Bug256-FormDataParsing.fs
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
module Suave.Tests.FormDataParsing
module Suave.Tests.FormDataParsing

open Expecto
open System
open System.IO
open System.Net.Http
open System.Reflection
open Suave
open Suave.Utils
open Suave.Logging
open Suave.Operators
open Suave.Filters
open Suave.RequestErrors
Expand All @@ -21,7 +18,7 @@ let app =
path "/gifs/echo"
>=> Writers.setMimeType "image/gif"
>=> warbler (fun ctx ->
let file = ctx.request.files.Head
let file = ctx.request.files[0]
//printfn "||| in suave, handing over to sendFile, file %s len %d"
// file.tempFilePath (FileInfo(file.tempFilePath).Length)
Files.sendFile file.tempFilePath false)
Expand Down
4 changes: 3 additions & 1 deletion src/Suave/Combinators.fs
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,8 @@ module TransferEncoding =

module Control =

open System.Collections.Generic

let CLOSE (ctx : HttpContext) =
{ ctx with
response =
Expand All @@ -888,7 +890,7 @@ module Control =
}
request =
{ ctx.request with
headers = [ "connection", "close" ]
headers = List<_>([ "connection", "close" ])
}
}
|> succeed
Expand Down
2 changes: 0 additions & 2 deletions src/Suave/Combinators.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -1082,8 +1082,6 @@ module ServerErrors =
/// Functions have signature f :: params... -> HttpContext -> HttpContext option.
module Filters =

open Suave.Logging

/// Match on the path
val path : pathAfterDomain:string -> WebPart

Expand Down
28 changes: 13 additions & 15 deletions src/Suave/ConnectionFacade.fs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ open System.Text
open Suave
open Suave.Utils
open Suave.Utils.Parsing
open Suave.Logging
open Suave.Sockets
open Suave.Sockets.Control
open Suave.Sockets.SocketOp.Operators
Expand All @@ -24,8 +23,8 @@ type ConnectionFacade(connection: Connection, runtime: HttpRuntime, connectionPo

let reader = connection.reader

let files = List<HttpUpload>()
let multiPartFields = List<string*string>()
let mutable files = List<HttpUpload>()
let mutable multiPartFields = List<string*string>()
let mutable _rawForm : byte array = [||]

let readFilePart boundary (headerParams : Dictionary<string,string>) fieldName contentType = socket {
Expand Down Expand Up @@ -77,10 +76,10 @@ type ConnectionFacade(connection: Connection, runtime: HttpRuntime, connectionPo
let! partHeaders = reader.readHeaders()

let! (contentDisposition : string) =
(partHeaders %% "content-disposition")
(partHeaders @@ "content-disposition")
@|! (None, "Missing 'content-disposition'")

match partHeaders %% "content-type" with
match partHeaders @@ "content-type" with
| Choice1Of2 contentType ->
let headerParams = headerParams contentDisposition
let! res = readFilePart boundary headerParams fieldName contentType
Expand Down Expand Up @@ -110,7 +109,7 @@ type ConnectionFacade(connection: Connection, runtime: HttpRuntime, connectionPo
let! partHeaders = reader.readHeaders()

let! (contentDisposition : string) =
(partHeaders %% "content-disposition")
(partHeaders @@ "content-disposition")
@|! (None, "Missing 'content-disposition'")

let headerParams = headerParams contentDisposition
Expand All @@ -123,7 +122,7 @@ type ConnectionFacade(connection: Connection, runtime: HttpRuntime, connectionPo
(headerParams.TryLookup "name" |> Choice.map (String.trimc '"'))
@|! (None, "Key 'name' was not present in 'content-disposition'")

match partHeaders %% "content-type" with
match partHeaders @@ "content-type" with
| Choice1Of2 x when String.startsWith "multipart/mixed" x ->
let subboundary = "--" + parseBoundary x
do! parseMultipartMixed fieldName subboundary
Expand Down Expand Up @@ -237,13 +236,13 @@ type ConnectionFacade(connection: Connection, runtime: HttpRuntime, connectionPo

// Respond with 400 Bad Request as
// per http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
let! rawHost = headers %% "host" @|! (None, "Missing 'Host' header")
let! rawHost = (headers @@ "host") @|! (None, "Missing 'Host' header")

if headers %% "expect" = Choice1Of2 "100-continue" then
if headers @@ "expect" = Choice1Of2 "100-continue" then
let! _ = httpOutput.run HttpRequest.empty Intermediate.CONTINUE
()

do! this.parsePostData runtime.maxContentLength (headers %% "content-length") (headers %% "content-type")
do! this.parsePostData runtime.maxContentLength (headers @@ "content-length") (headers @@ "content-type")

let request =
{ httpVersion = httpVersion
Expand All @@ -254,13 +253,12 @@ type ConnectionFacade(connection: Connection, runtime: HttpRuntime, connectionPo
headers = headers
rawForm = _rawForm
rawQuery = rawQuery
files = Seq.toList files
multiPartFields = Seq.toList multiPartFields
trace = TraceHeader.parseTraceHeaders headers }
files = files
multiPartFields = multiPartFields }

// Clear form data before exit
files.Clear()
multiPartFields.Clear()
files <- List<_>()
multiPartFields <- List<_>()
_rawForm <- [||]

return request
Expand Down
1 change: 1 addition & 0 deletions src/Suave/Cookie.fs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ module Cookie =
/// Get yourself a dictionary of cookie-name to Cookie.
member x.cookies =
x.headers
|> Seq.toList
|> List.filter (fun (name, _) -> name.Equals "cookie")
|> List.collect (snd >> parseCookies)
|> List.fold (fun cookies cookie ->
Expand Down
15 changes: 7 additions & 8 deletions src/Suave/Headers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ module Headers =
match qVal
|> Seq.map (fun x -> x.Substring 2)
|> Seq.choose parseDecimal
//|> Seq.tryHead with // TODO: F# 4
|> Seq.tryFind (fun _ -> true) with
| Some d -> d
| None -> 1m
Expand All @@ -96,13 +95,13 @@ module Headers =
Seq.append [mediaRange] others
|> String.concat ";"
mediaRange, quality)
//|> Seq.sortByDescending snd // TODO: F# 4
|> Seq.sortBy (fun (_, q) -> -q)

open Suave.Utils

/// Headers are lowercased, so can use string.Equals
let getAll (target : NameValueList) (key : string) =
match target |> List.choose (fun (a, b) -> if a.Equals key then Some b else None) with
| [] -> Choice2Of2 ("Couldn't find key '" + key + "' in NameValueList")
| l -> Choice1Of2 l
open System.Collections.Generic
let getAll (target : List<string*string>) (key : string) =
let results = target |> Seq.choose (fun (a, b) -> if a.Equals key then Some b else None)
if Seq.isEmpty results then
Choice2Of2 ("Couldn't find key '" + key + "'")
else
Choice1Of2 results
Loading

0 comments on commit d08f7a4

Please sign in to comment.