diff --git a/fsharp-backend/paket.dependencies b/fsharp-backend/paket.dependencies index e507440624..26e894f103 100644 --- a/fsharp-backend/paket.dependencies +++ b/fsharp-backend/paket.dependencies @@ -27,6 +27,7 @@ nuget FsCheck = 2.16.4 nuget FSharp.Compiler.Service = 41.0.3 nuget NReco.Logging.File = 1.1.3 nuget SimpleBase = 3.1.0 +nuget ChunkDecoder = 1.0.4.1 // Services nuget Lib.AspNetCore.ServerTiming = 4.3.0 diff --git a/fsharp-backend/paket.lock b/fsharp-backend/paket.lock index a16a4418cb..d010b0f62b 100644 --- a/fsharp-backend/paket.lock +++ b/fsharp-backend/paket.lock @@ -8,6 +8,7 @@ NUGET AspNetCore.HealthChecks.NpgSql (6.0.1) Microsoft.Extensions.Diagnostics.HealthChecks (>= 6.0) Npgsql (>= 6.0) + ChunkDecoder (1.0.4.1) Expecto (9.0.4) FSharp.Core (>= 4.6) Mono.Cecil (>= 0.11.3) diff --git a/fsharp-backend/src/BackendOnlyStdLib/HttpClient.fs b/fsharp-backend/src/BackendOnlyStdLib/HttpClient.fs index 87224546e9..5bb6099191 100644 --- a/fsharp-backend/src/BackendOnlyStdLib/HttpClient.fs +++ b/fsharp-backend/src/BackendOnlyStdLib/HttpClient.fs @@ -195,6 +195,7 @@ let httpCall' | "gzip" -> new GZipStream(responseStream, decompress) | "deflate" -> new DeflateStream(responseStream, decompress) | "" -> responseStream + // FSTODO: test other format such as zstd | _ -> raise (InvalidEncodingException(int response.StatusCode)) use memoryStream = new MemoryStream() diff --git a/fsharp-backend/src/BwdServer/BwdServer.fsproj b/fsharp-backend/src/BwdServer/BwdServer.fsproj index c3048c2749..16f773d3b3 100644 --- a/fsharp-backend/src/BwdServer/BwdServer.fsproj +++ b/fsharp-backend/src/BwdServer/BwdServer.fsproj @@ -9,7 +9,7 @@ true true true - false @@ -17,6 +17,7 @@ + diff --git a/fsharp-backend/src/BwdServer/Compression.fs b/fsharp-backend/src/BwdServer/Compression.fs new file mode 100644 index 0000000000..6e27257c30 --- /dev/null +++ b/fsharp-backend/src/BwdServer/Compression.fs @@ -0,0 +1,51 @@ +module BwdServer.Compression + +open FSharp.Control.Tasks +open System.Threading.Tasks + +open System +open Microsoft.AspNetCore +open Microsoft.AspNetCore.Builder +open Microsoft.AspNetCore.Hosting +open Microsoft.AspNetCore.Http +open Microsoft.AspNetCore.Http.Extensions +open Microsoft.AspNetCore.Http.Abstractions +open Microsoft.Extensions.Logging +open Microsoft.Extensions.Hosting +open Microsoft.Extensions.DependencyInjection +open Microsoft.AspNetCore.ResponseCompression +open Microsoft.Extensions.DependencyInjection.Extensions + +type CustomCompressionProvider(services, options) = + inherit ResponseCompressionProvider(services, options) + override this.ShouldCompressResponse(ctx : HttpContext) : bool = + // Compress responses unless they're too small + let default_ = base.ShouldCompressResponse ctx + let tooSmall = + // This was the setting we had in the ocaml nginx + if ctx.Response.ContentLength.HasValue then + ctx.Response.ContentLength.Value < 1024 + else + false + default_ && not tooSmall + +let configureServices (services : IServiceCollection) : IServiceCollection = + let configureOptions (options : ResponseCompressionOptions) : unit = + // CLEANUP: This is set to the same values as we used in nginx for the ocaml + // bwdserver. By default, .net also had a few others: text/javascript, + // application/xml, text/xml, text/json, application/wasm. They aren't that + // interesting to us right now. + options.MimeTypes <- + [ "text/html" + "text/plain" + "text/css" + "application/javascript" + "application/json" ] + services.Configure(configureOptions) |> ignore + services.TryAddSingleton() + services + +let addToApp (app : IApplicationBuilder) : IApplicationBuilder = + // FSTODO do we need to do anything to use our custom provider with the default middleware? + // https://github.com/dotnet/aspnetcore/tree/c85baf8db0c72ae8e68643029d514b2e737c9fae/src/Middleware/ResponseCompression/src + app.UseResponseCompression() diff --git a/fsharp-backend/src/BwdServer/Server.fs b/fsharp-backend/src/BwdServer/Server.fs index cb9fc4fab8..0a3c08db66 100644 --- a/fsharp-backend/src/BwdServer/Server.fs +++ b/fsharp-backend/src/BwdServer/Server.fs @@ -19,6 +19,8 @@ open Microsoft.AspNetCore.Http.Abstractions open Microsoft.Extensions.Logging open Microsoft.Extensions.Hosting open Microsoft.Extensions.DependencyInjection +open Microsoft.AspNetCore.ResponseCompression +open Microsoft.Extensions.DependencyInjection.Extensions type StringValues = Microsoft.Extensions.Primitives.StringValues @@ -470,6 +472,7 @@ let configureApp (healthCheckPort : int) (app : IApplicationBuilder) = (person, metadata) Rollbar.AspNet.addRollbarToApp app rollbarCtxToMetadata None + |> Compression.addToApp |> fun app -> app.UseRouting() // must go after UseRouting |> Kubernetes.configureApp healthCheckPort @@ -477,6 +480,7 @@ let configureApp (healthCheckPort : int) (app : IApplicationBuilder) = let configureServices (services : IServiceCollection) : unit = services + |> Compression.configureServices |> Kubernetes.configureServices [ LibBackend.Init.legacyServerCheck ] |> Rollbar.AspNet.addRollbarToServices |> Telemetry.AspNet.addTelemetryToServices "BwdServer" Telemetry.TraceDBQueries diff --git a/fsharp-backend/tests/TestUtils/TestUtils.fs b/fsharp-backend/tests/TestUtils/TestUtils.fs index a6a44784fd..6427b40085 100644 --- a/fsharp-backend/tests/TestUtils/TestUtils.fs +++ b/fsharp-backend/tests/TestUtils/TestUtils.fs @@ -5,6 +5,9 @@ open Expecto open System.Threading.Tasks open FSharp.Control.Tasks +open System.IO +open System.IO.Compression + open Npgsql.FSharp open Npgsql open LibBackend.Db @@ -1000,7 +1003,7 @@ let sampleDvals : List = // Utilties shared among tests module Http = - type T = { status : string; headers : (string * string) list; body : byte array } + type T = { status : string; headers : List; body : byte array } let setHeadersToCRLF (text : byte array) : byte array = // We keep our test files with an LF line ending, but the HTTP spec @@ -1021,6 +1024,70 @@ module Http = [ b ]) |> List.toArray + // Decompress + let decompressIfNeeded + (headers : List) + (body : byte array) + : byte array = + let contentEncodingHeader = + headers + |> List.find (fun (k, v) -> String.toLowercase k = "content-encoding") + |> Option.map Tuple2.second + |> Option.map String.toLowercase + + // If the transfer-encoding=chunked header is set, we need to process it before + // we have a gzip/brotli/etc output + let body = + // Only decode the transfer-encoding in order to decompress the stream. We have + // tests for the transfer-encoding format and we don't want to break them by + // transfer-encoding test bodies + if Option.isSome contentEncodingHeader then + let isTransferEncodingChunked = + headers + |> List.find (fun (k, v) -> + String.toLowercase k = "transfer-encoding" + && String.toLowercase v = "chunked") + |> Option.isSome + if isTransferEncodingChunked then + let decoder = new ChunkDecoder.Decoder() + let mutable (byteArray : byte array) = null + // asp.net doesn't add the final sequence required by + // `transfer-encoding:chunked`, relying instead on closing the connection + // to indicate that the data is complete. However, the ChunkDecoder library + // does not support this, and hangs while waiting on the final chunk. We + // add the final chunk ourselves to allow the library to finish its work. + let body = + match body with + | [||] -> body + | body -> + let bytesToAppend = UTF8.toBytes "0\r\n" + Array.append body bytesToAppend + let success = decoder.Decode(body, &byteArray) + if not success then Exception.raiseInternal "could not dechunk chunks" [] + byteArray + else + body + else + body + + match contentEncodingHeader with + | Some "gzip" -> + let inputStream = new MemoryStream(body) + use decompressionStream = + new GZipStream(inputStream, CompressionMode.Decompress) + use outputStream = new MemoryStream() + decompressionStream.CopyTo(outputStream) + outputStream.ToArray() + | Some "br" -> + let inputStream = new MemoryStream(body) + use decompressionStream = + new BrotliStream(inputStream, CompressionMode.Decompress) + use outputStream = new MemoryStream() + decompressionStream.CopyTo(outputStream) + outputStream.ToArray() + | Some ce -> Exception.raiseInternal $"unsupported content encoding {ce}" [] + | None -> body + let split (response : byte array) : T = // read a single line of bytes (a line ends with \r\n) let rec consume (existing : byte list) (l : byte list) : byte list * byte list = diff --git a/fsharp-backend/tests/TestUtils/paket.references b/fsharp-backend/tests/TestUtils/paket.references index f1fd67956c..55e3a942ca 100644 --- a/fsharp-backend/tests/TestUtils/paket.references +++ b/fsharp-backend/tests/TestUtils/paket.references @@ -1,3 +1,4 @@ Expecto FSharp.Compiler.Service NReco.Logging.File +ChunkDecoder diff --git a/fsharp-backend/tests/Tests/BwdServer.Tests.fs b/fsharp-backend/tests/Tests/BwdServer.Tests.fs index 0c9235b705..55405c88ff 100644 --- a/fsharp-backend/tests/Tests/BwdServer.Tests.fs +++ b/fsharp-backend/tests/Tests/BwdServer.Tests.fs @@ -21,6 +21,7 @@ module RT = LibExecution.RuntimeTypes module PT = LibExecution.ProgramTypes module Routing = LibBackend.Routing module Canvas = LibBackend.Canvas +module DvalRepr = LibExecution.DvalReprExternal open TestUtils.TestUtils open System.Text.Json @@ -304,54 +305,47 @@ let createClient (port : int) : Task = return client } -/// Makes the test request to one of the servers, -/// testing the response matches expectations -let runTestRequest +let prepareRequest + (request : byte array) + (host : string) (canvasName : string) - (testRequest : byte array) - (testResponse : byte array) - (server : Server) - : Task = - task { - let port = - match server with - | OCaml -> TestConfig.ocamlServerNginxPort - | FSharp -> TestConfig.bwdServerBackendPort - - let host = $"{canvasName}.builtwithdark.localhost:{port}" - - let request = - testRequest - |> replaceByteStrings "HOST" host - |> replaceByteStrings "CANVAS" canvasName - |> Http.setHeadersToCRLF + : byte array = + let request = + request + |> replaceByteStrings "HOST" host + |> replaceByteStrings "CANVAS" canvasName + |> Http.setHeadersToCRLF + + // Check body matches content-length + let incorrectContentTypeAllowed = + request + |> UTF8.ofBytesWithReplacement + |> String.includes "ALLOW-INCORRECT-CONTENT-LENGTH" + + if not incorrectContentTypeAllowed then + let parsedTestRequest = Http.split request + let contentLength = + parsedTestRequest.headers + |> List.find (fun (k, v) -> String.toLowercase k = "content-length") + match contentLength with + | None -> () + | Some (_, v) -> + if String.includes "ALLOW-INCORRECT-CONTENT-LENGTH" v then + () + else + Expect.equal parsedTestRequest.body.Length (int v) "" - // Check body matches content-length - let incorrectContentTypeAllowed = - testRequest - |> UTF8.ofBytesWithReplacement - |> String.includes "ALLOW-INCORRECT-CONTENT-LENGTH" - - if not incorrectContentTypeAllowed then - let parsedTestRequest = Http.split request - let contentLength = - parsedTestRequest.headers - |> List.find (fun (k, v) -> String.toLowercase k = "content-length") - match contentLength with - | None -> () - | Some (_, v) -> - if String.includes "ALLOW-INCORRECT-CONTENT-LENGTH" v then - () - else - Expect.equal parsedTestRequest.body.Length (int v) "" + // Check input LENGTH not set + if request |> UTF8.ofBytesWithReplacement |> String.includes "LENGTH" + && not incorrectContentTypeAllowed then // false alarm as also have LENGTH in it + Expect.isFalse true "LENGTH substitution not done on request" - // Check input LENGTH not set - if testRequest |> UTF8.ofBytesWithReplacement |> String.includes "LENGTH" - && not incorrectContentTypeAllowed then // false alarm as also have LENGTH in it - Expect.isFalse true "LENGTH substitution not done on request" + request +let makeRequest (request : byte array) (port : int) : Task = + task { // Make the request - use! client = createClient (port) + use! client = createClient port use stream = client.GetStream() stream.ReadTimeout <- 1000 // responses should be instant, right? @@ -365,9 +359,32 @@ let runTestRequest stream.Close() client.Close() let response = Array.take byteCount responseBuffer + return Http.split response + } + + + +/// Makes the test request to one of the servers, +/// testing the response matches expectations +let runTestRequest + (canvasName : string) + (testRequest : byte array) + (testResponse : byte array) + (server : Server) + : Task = + task { + let port = + match server with + | OCaml -> TestConfig.ocamlServerNginxPort + | FSharp -> TestConfig.bwdServerBackendPort + + let host = $"{canvasName}.builtwithdark.localhost:{port}" + + let request = prepareRequest testRequest host canvasName + let! actual = makeRequest request port // Prepare expected response - let expectedResponse = + let expected = testResponse |> splitAtNewlines |> List.filterMap (fun line -> @@ -396,27 +413,31 @@ let runTestRequest |> replaceByteStrings "HOST" host |> replaceByteStrings "CANVAS" canvasName |> Http.setHeadersToCRLF + |> Http.split - // Parse and normalize the response - let actual = Http.split response - let expected = Http.split expectedResponse + // Normalize the responses let expectedHeaders = normalizeExpectedHeaders expected.headers actual.body let actualHeaders = normalizeActualHeaders actual.headers + // Decompress the body if returned with a content-encoding. Throws an exception + // if content-encoding is set and the body is not compressed. This lets us test + // that the server returns compressed content + let actual = + { actual with body = Http.decompressIfNeeded actual.headers actual.body } + // Test as json or strings let asJson = try Some( - LibExecution.DvalReprExternal.parseJson (UTF8.ofBytesUnsafe actual.body), - LibExecution.DvalReprExternal.parseJson (UTF8.ofBytesUnsafe expected.body) + DvalRepr.parseJson (UTF8.ofBytesUnsafe actual.body), + DvalRepr.parseJson (UTF8.ofBytesUnsafe expected.body) ) with | e -> None match asJson with | Some (aJson, eJson) -> - let serialize (json : JsonDocument) = - LibExecution.DvalReprExternal.writePrettyJson json.WriteTo + let serialize (json : JsonDocument) = DvalRepr.writePrettyJson json.WriteTo Expect.equal (actual.status, actualHeaders, serialize aJson) (expected.status, expectedHeaders, serialize eJson) @@ -457,6 +478,7 @@ let t (filename : string) = if shouldSkip then skiptest $"underscore test - {testName}" + else do! callServer OCaml // check OCaml to see if we got the right answer do! callServer FSharp // test F# impl diff --git a/fsharp-backend/tests/httptestfiles/request-header-accept-encoding-gzip.test b/fsharp-backend/tests/httptestfiles/request-header-accept-encoding-gzip.test index 3cfcd8cf6c..2b6851981d 100644 --- a/fsharp-backend/tests/httptestfiles/request-header-accept-encoding-gzip.test +++ b/fsharp-backend/tests/httptestfiles/request-header-accept-encoding-gzip.test @@ -23,4 +23,5 @@ Connection: keep-alive // OCAMLONLY Strict-Transport-Security: max-age=31536000; includeSubDomains; preload // FSHARPONLY Content-Length: LENGTH + "2019-09-07T22:44:25Z" \ No newline at end of file diff --git a/fsharp-backend/tests/httptestfiles/request-header-accept-encoding-many.test b/fsharp-backend/tests/httptestfiles/request-header-accept-encoding-many.test index b492a2d948..05b1803925 100644 --- a/fsharp-backend/tests/httptestfiles/request-header-accept-encoding-many.test +++ b/fsharp-backend/tests/httptestfiles/request-header-accept-encoding-many.test @@ -23,4 +23,5 @@ Connection: keep-alive // OCAMLONLY Strict-Transport-Security: max-age=31536000; includeSubDomains; preload // FSHARPONLY Content-Length: LENGTH + "2019-09-07T22:44:25Z" \ No newline at end of file diff --git a/fsharp-backend/tests/httptestfiles/request-header-accept-encoding-star.test b/fsharp-backend/tests/httptestfiles/request-header-accept-encoding-star.test index 87572f4897..410434d361 100644 --- a/fsharp-backend/tests/httptestfiles/request-header-accept-encoding-star.test +++ b/fsharp-backend/tests/httptestfiles/request-header-accept-encoding-star.test @@ -23,4 +23,5 @@ Connection: keep-alive // OCAMLONLY Strict-Transport-Security: max-age=31536000; includeSubDomains; preload // FSHARPONLY Content-Length: LENGTH + "2019-09-07T22:44:25Z" \ No newline at end of file diff --git a/fsharp-backend/tests/httptestfiles/request-header-content-encoding-brotli.test b/fsharp-backend/tests/httptestfiles/request-header-content-encoding-brotli.test new file mode 100644 index 0000000000..efd6eed1ff --- /dev/null +++ b/fsharp-backend/tests/httptestfiles/request-header-content-encoding-brotli.test @@ -0,0 +1,550 @@ +[http-handler GET /] +// this is https://csshake.surge.sh/csshake-default.css, pasted twice to get over 1024 characters. +(let body = + "/*! * * * * * * * * * * * * * * * * * * * * + CSShake :: shake + v1.5.0 + CSS classes to move your DOM + (c) 2015 @elrumordelaluz + http://elrumordelaluz.github.io/csshake/ + Licensed under MIT + \* * * * * * * * * * * * * * * * * * * * */ + .shake { + display: inherit; + transform-origin: center center; } + + .shake-freeze, + .shake-constant.shake-constant--hover:hover, + .shake-trigger:hover .shake-constant.shake-constant--hover { + animation-play-state: paused; } + + .shake-freeze:hover, + .shake-trigger:hover .shake-freeze, .shake:hover, + .shake-trigger:hover .shake { + animation-play-state: running; } + + @keyframes shake { + 2% { + transform: translate(-0.5px, 2.5px) rotate(-0.5deg); } + 4% { + transform: translate(0.5px, -1.5px) rotate(-0.5deg); } + 6% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 8% { + transform: translate(0.5px, 2.5px) rotate(-0.5deg); } + 10% { + transform: translate(-1.5px, 2.5px) rotate(1.5deg); } + 12% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 14% { + transform: translate(2.5px, 2.5px) rotate(1.5deg); } + 16% { + transform: translate(1.5px, 1.5px) rotate(0.5deg); } + 18% { + transform: translate(0.5px, 2.5px) rotate(1.5deg); } + 20% { + transform: translate(1.5px, -1.5px) rotate(-0.5deg); } + 22% { + transform: translate(0.5px, 1.5px) rotate(1.5deg); } + 24% { + transform: translate(-0.5px, 1.5px) rotate(0.5deg); } + 26% { + transform: translate(-1.5px, -1.5px) rotate(0.5deg); } + 28% { + transform: translate(1.5px, 2.5px) rotate(1.5deg); } + 30% { + transform: translate(2.5px, -1.5px) rotate(1.5deg); } + 32% { + transform: translate(1.5px, 1.5px) rotate(1.5deg); } + 34% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 36% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 38% { + transform: translate(0.5px, 0.5px) rotate(0.5deg); } + 40% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 42% { + transform: translate(0.5px, -0.5px) rotate(0.5deg); } + 44% { + transform: translate(-0.5px, -0.5px) rotate(-0.5deg); } + 46% { + transform: translate(1.5px, 2.5px) rotate(-0.5deg); } + 48% { + transform: translate(2.5px, 2.5px) rotate(1.5deg); } + 50% { + transform: translate(0.5px, 2.5px) rotate(1.5deg); } + 52% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 54% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 56% { + transform: translate(1.5px, 1.5px) rotate(0.5deg); } + 58% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 60% { + transform: translate(-0.5px, 0.5px) rotate(-0.5deg); } + 62% { + transform: translate(2.5px, -1.5px) rotate(1.5deg); } + 64% { + transform: translate(2.5px, -0.5px) rotate(0.5deg); } + 66% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 68% { + transform: translate(-1.5px, -0.5px) rotate(0.5deg); } + 70% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 72% { + transform: translate(1.5px, 0.5px) rotate(1.5deg); } + 74% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 76% { + transform: translate(-1.5px, -0.5px) rotate(0.5deg); } + 78% { + transform: translate(-1.5px, -1.5px) rotate(-0.5deg); } + 80% { + transform: translate(-0.5px, -0.5px) rotate(0.5deg); } + 82% { + transform: translate(-0.5px, 1.5px) rotate(1.5deg); } + 84% { + transform: translate(-1.5px, 2.5px) rotate(-0.5deg); } + 86% { + transform: translate(-1.5px, -0.5px) rotate(-0.5deg); } + 88% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 90% { + transform: translate(2.5px, -1.5px) rotate(-0.5deg); } + 92% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 94% { + transform: translate(0.5px, 1.5px) rotate(-0.5deg); } + 96% { + transform: translate(1.5px, 2.5px) rotate(0.5deg); } + 98% { + transform: translate(2.5px, -0.5px) rotate(1.5deg); } + 0%, 100% { + transform: translate(0, 0) rotate(0); } } + + .shake:hover, + .shake-trigger:hover .shake, .shake.shake-freeze, .shake.shake-constant { + animation-name: shake; + animation-duration: 100ms; + animation-timing-function: ease-in-out; + animation-iteration-count: infinite; } + /*! * * * * * * * * * * * * * * * * * * * * + CSShake :: shake + v1.5.0 + CSS classes to move your DOM + (c) 2015 @elrumordelaluz + http://elrumordelaluz.github.io/csshake/ + Licensed under MIT + \* * * * * * * * * * * * * * * * * * * * */ + .shake { + display: inherit; + transform-origin: center center; } + + .shake-freeze, + .shake-constant.shake-constant--hover:hover, + .shake-trigger:hover .shake-constant.shake-constant--hover { + animation-play-state: paused; } + + .shake-freeze:hover, + .shake-trigger:hover .shake-freeze, .shake:hover, + .shake-trigger:hover .shake { + animation-play-state: running; } + + @keyframes shake { + 2% { + transform: translate(-0.5px, 2.5px) rotate(-0.5deg); } + 4% { + transform: translate(0.5px, -1.5px) rotate(-0.5deg); } + 6% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 8% { + transform: translate(0.5px, 2.5px) rotate(-0.5deg); } + 10% { + transform: translate(-1.5px, 2.5px) rotate(1.5deg); } + 12% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 14% { + transform: translate(2.5px, 2.5px) rotate(1.5deg); } + 16% { + transform: translate(1.5px, 1.5px) rotate(0.5deg); } + 18% { + transform: translate(0.5px, 2.5px) rotate(1.5deg); } + 20% { + transform: translate(1.5px, -1.5px) rotate(-0.5deg); } + 22% { + transform: translate(0.5px, 1.5px) rotate(1.5deg); } + 24% { + transform: translate(-0.5px, 1.5px) rotate(0.5deg); } + 26% { + transform: translate(-1.5px, -1.5px) rotate(0.5deg); } + 28% { + transform: translate(1.5px, 2.5px) rotate(1.5deg); } + 30% { + transform: translate(2.5px, -1.5px) rotate(1.5deg); } + 32% { + transform: translate(1.5px, 1.5px) rotate(1.5deg); } + 34% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 36% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 38% { + transform: translate(0.5px, 0.5px) rotate(0.5deg); } + 40% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 42% { + transform: translate(0.5px, -0.5px) rotate(0.5deg); } + 44% { + transform: translate(-0.5px, -0.5px) rotate(-0.5deg); } + 46% { + transform: translate(1.5px, 2.5px) rotate(-0.5deg); } + 48% { + transform: translate(2.5px, 2.5px) rotate(1.5deg); } + 50% { + transform: translate(0.5px, 2.5px) rotate(1.5deg); } + 52% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 54% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 56% { + transform: translate(1.5px, 1.5px) rotate(0.5deg); } + 58% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 60% { + transform: translate(-0.5px, 0.5px) rotate(-0.5deg); } + 62% { + transform: translate(2.5px, -1.5px) rotate(1.5deg); } + 64% { + transform: translate(2.5px, -0.5px) rotate(0.5deg); } + 66% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 68% { + transform: translate(-1.5px, -0.5px) rotate(0.5deg); } + 70% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 72% { + transform: translate(1.5px, 0.5px) rotate(1.5deg); } + 74% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 76% { + transform: translate(-1.5px, -0.5px) rotate(0.5deg); } + 78% { + transform: translate(-1.5px, -1.5px) rotate(-0.5deg); } + 80% { + transform: translate(-0.5px, -0.5px) rotate(0.5deg); } + 82% { + transform: translate(-0.5px, 1.5px) rotate(1.5deg); } + 84% { + transform: translate(-1.5px, 2.5px) rotate(-0.5deg); } + 86% { + transform: translate(-1.5px, -0.5px) rotate(-0.5deg); } + 88% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 90% { + transform: translate(2.5px, -1.5px) rotate(-0.5deg); } + 92% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 94% { + transform: translate(0.5px, 1.5px) rotate(-0.5deg); } + 96% { + transform: translate(1.5px, 2.5px) rotate(0.5deg); } + 98% { + transform: translate(2.5px, -0.5px) rotate(1.5deg); } + 0%, 100% { + transform: translate(0, 0) rotate(0); } } + + .shake:hover, + .shake-trigger:hover .shake, .shake.shake-freeze, .shake.shake-constant { + animation-name: shake; + animation-duration: 100ms; + animation-timing-function: ease-in-out; + animation-iteration-count: infinite; }" + Http.responseWithText body 200) + +[request] +GET / HTTP/1.1 +Host: HOST +Date: Sun, 08 Nov 2020 15:38:01 GMT +Content-Length: 0 +Accept-encoding: br + + + +[response] +HTTP/1.1 200 OK +Date: xxx, xx xxx xxxx xx:xx:xx xxx +Content-Type: text/plain +Content-Encoding: br // FSHARPONLY +Content-Length: LENGTH // OCAMLONLY +Access-Control-Allow-Origin: * // FSHARPONLY +access-control-allow-origin: * // OCAMLONLY +x-darklang-execution-id: 0123456789 +Server: nginx/1.16.1 // OCAMLONLY +Server: darklang // FSHARPONLY +Connection: keep-alive // OCAMLONLY +Vary: Accept-Encoding // FSHARPONLY +Strict-Transport-Security: max-age=31536000; includeSubDomains; preload // FSHARPONLY +Transfer-Encoding: chunked // FSHARPONLY + +/*! * * * * * * * * * * * * * * * * * * * * + CSShake :: shake + v1.5.0 + CSS classes to move your DOM + (c) 2015 @elrumordelaluz + http://elrumordelaluz.github.io/csshake/ + Licensed under MIT + \* * * * * * * * * * * * * * * * * * * * */ + .shake { + display: inherit; + transform-origin: center center; } + + .shake-freeze, + .shake-constant.shake-constant--hover:hover, + .shake-trigger:hover .shake-constant.shake-constant--hover { + animation-play-state: paused; } + + .shake-freeze:hover, + .shake-trigger:hover .shake-freeze, .shake:hover, + .shake-trigger:hover .shake { + animation-play-state: running; } + + @keyframes shake { + 2% { + transform: translate(-0.5px, 2.5px) rotate(-0.5deg); } + 4% { + transform: translate(0.5px, -1.5px) rotate(-0.5deg); } + 6% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 8% { + transform: translate(0.5px, 2.5px) rotate(-0.5deg); } + 10% { + transform: translate(-1.5px, 2.5px) rotate(1.5deg); } + 12% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 14% { + transform: translate(2.5px, 2.5px) rotate(1.5deg); } + 16% { + transform: translate(1.5px, 1.5px) rotate(0.5deg); } + 18% { + transform: translate(0.5px, 2.5px) rotate(1.5deg); } + 20% { + transform: translate(1.5px, -1.5px) rotate(-0.5deg); } + 22% { + transform: translate(0.5px, 1.5px) rotate(1.5deg); } + 24% { + transform: translate(-0.5px, 1.5px) rotate(0.5deg); } + 26% { + transform: translate(-1.5px, -1.5px) rotate(0.5deg); } + 28% { + transform: translate(1.5px, 2.5px) rotate(1.5deg); } + 30% { + transform: translate(2.5px, -1.5px) rotate(1.5deg); } + 32% { + transform: translate(1.5px, 1.5px) rotate(1.5deg); } + 34% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 36% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 38% { + transform: translate(0.5px, 0.5px) rotate(0.5deg); } + 40% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 42% { + transform: translate(0.5px, -0.5px) rotate(0.5deg); } + 44% { + transform: translate(-0.5px, -0.5px) rotate(-0.5deg); } + 46% { + transform: translate(1.5px, 2.5px) rotate(-0.5deg); } + 48% { + transform: translate(2.5px, 2.5px) rotate(1.5deg); } + 50% { + transform: translate(0.5px, 2.5px) rotate(1.5deg); } + 52% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 54% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 56% { + transform: translate(1.5px, 1.5px) rotate(0.5deg); } + 58% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 60% { + transform: translate(-0.5px, 0.5px) rotate(-0.5deg); } + 62% { + transform: translate(2.5px, -1.5px) rotate(1.5deg); } + 64% { + transform: translate(2.5px, -0.5px) rotate(0.5deg); } + 66% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 68% { + transform: translate(-1.5px, -0.5px) rotate(0.5deg); } + 70% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 72% { + transform: translate(1.5px, 0.5px) rotate(1.5deg); } + 74% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 76% { + transform: translate(-1.5px, -0.5px) rotate(0.5deg); } + 78% { + transform: translate(-1.5px, -1.5px) rotate(-0.5deg); } + 80% { + transform: translate(-0.5px, -0.5px) rotate(0.5deg); } + 82% { + transform: translate(-0.5px, 1.5px) rotate(1.5deg); } + 84% { + transform: translate(-1.5px, 2.5px) rotate(-0.5deg); } + 86% { + transform: translate(-1.5px, -0.5px) rotate(-0.5deg); } + 88% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 90% { + transform: translate(2.5px, -1.5px) rotate(-0.5deg); } + 92% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 94% { + transform: translate(0.5px, 1.5px) rotate(-0.5deg); } + 96% { + transform: translate(1.5px, 2.5px) rotate(0.5deg); } + 98% { + transform: translate(2.5px, -0.5px) rotate(1.5deg); } + 0%, 100% { + transform: translate(0, 0) rotate(0); } } + + .shake:hover, + .shake-trigger:hover .shake, .shake.shake-freeze, .shake.shake-constant { + animation-name: shake; + animation-duration: 100ms; + animation-timing-function: ease-in-out; + animation-iteration-count: infinite; } + /*! * * * * * * * * * * * * * * * * * * * * + CSShake :: shake + v1.5.0 + CSS classes to move your DOM + (c) 2015 @elrumordelaluz + http://elrumordelaluz.github.io/csshake/ + Licensed under MIT + \* * * * * * * * * * * * * * * * * * * * */ + .shake { + display: inherit; + transform-origin: center center; } + + .shake-freeze, + .shake-constant.shake-constant--hover:hover, + .shake-trigger:hover .shake-constant.shake-constant--hover { + animation-play-state: paused; } + + .shake-freeze:hover, + .shake-trigger:hover .shake-freeze, .shake:hover, + .shake-trigger:hover .shake { + animation-play-state: running; } + + @keyframes shake { + 2% { + transform: translate(-0.5px, 2.5px) rotate(-0.5deg); } + 4% { + transform: translate(0.5px, -1.5px) rotate(-0.5deg); } + 6% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 8% { + transform: translate(0.5px, 2.5px) rotate(-0.5deg); } + 10% { + transform: translate(-1.5px, 2.5px) rotate(1.5deg); } + 12% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 14% { + transform: translate(2.5px, 2.5px) rotate(1.5deg); } + 16% { + transform: translate(1.5px, 1.5px) rotate(0.5deg); } + 18% { + transform: translate(0.5px, 2.5px) rotate(1.5deg); } + 20% { + transform: translate(1.5px, -1.5px) rotate(-0.5deg); } + 22% { + transform: translate(0.5px, 1.5px) rotate(1.5deg); } + 24% { + transform: translate(-0.5px, 1.5px) rotate(0.5deg); } + 26% { + transform: translate(-1.5px, -1.5px) rotate(0.5deg); } + 28% { + transform: translate(1.5px, 2.5px) rotate(1.5deg); } + 30% { + transform: translate(2.5px, -1.5px) rotate(1.5deg); } + 32% { + transform: translate(1.5px, 1.5px) rotate(1.5deg); } + 34% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 36% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 38% { + transform: translate(0.5px, 0.5px) rotate(0.5deg); } + 40% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 42% { + transform: translate(0.5px, -0.5px) rotate(0.5deg); } + 44% { + transform: translate(-0.5px, -0.5px) rotate(-0.5deg); } + 46% { + transform: translate(1.5px, 2.5px) rotate(-0.5deg); } + 48% { + transform: translate(2.5px, 2.5px) rotate(1.5deg); } + 50% { + transform: translate(0.5px, 2.5px) rotate(1.5deg); } + 52% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 54% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 56% { + transform: translate(1.5px, 1.5px) rotate(0.5deg); } + 58% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 60% { + transform: translate(-0.5px, 0.5px) rotate(-0.5deg); } + 62% { + transform: translate(2.5px, -1.5px) rotate(1.5deg); } + 64% { + transform: translate(2.5px, -0.5px) rotate(0.5deg); } + 66% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 68% { + transform: translate(-1.5px, -0.5px) rotate(0.5deg); } + 70% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 72% { + transform: translate(1.5px, 0.5px) rotate(1.5deg); } + 74% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 76% { + transform: translate(-1.5px, -0.5px) rotate(0.5deg); } + 78% { + transform: translate(-1.5px, -1.5px) rotate(-0.5deg); } + 80% { + transform: translate(-0.5px, -0.5px) rotate(0.5deg); } + 82% { + transform: translate(-0.5px, 1.5px) rotate(1.5deg); } + 84% { + transform: translate(-1.5px, 2.5px) rotate(-0.5deg); } + 86% { + transform: translate(-1.5px, -0.5px) rotate(-0.5deg); } + 88% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 90% { + transform: translate(2.5px, -1.5px) rotate(-0.5deg); } + 92% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 94% { + transform: translate(0.5px, 1.5px) rotate(-0.5deg); } + 96% { + transform: translate(1.5px, 2.5px) rotate(0.5deg); } + 98% { + transform: translate(2.5px, -0.5px) rotate(1.5deg); } + 0%, 100% { + transform: translate(0, 0) rotate(0); } } + + .shake:hover, + .shake-trigger:hover .shake, .shake.shake-freeze, .shake.shake-constant { + animation-name: shake; + animation-duration: 100ms; + animation-timing-function: ease-in-out; + animation-iteration-count: infinite; } \ No newline at end of file diff --git a/fsharp-backend/tests/httptestfiles/request-header-content-encoding-gzip.test b/fsharp-backend/tests/httptestfiles/request-header-content-encoding-gzip.test new file mode 100644 index 0000000000..1c2baa9725 --- /dev/null +++ b/fsharp-backend/tests/httptestfiles/request-header-content-encoding-gzip.test @@ -0,0 +1,549 @@ +[http-handler GET /] +// this is https://csshake.surge.sh/csshake-default.css, pasted twice to get over 1024 characters. +(let body = + "/*! * * * * * * * * * * * * * * * * * * * * + CSShake :: shake + v1.5.0 + CSS classes to move your DOM + (c) 2015 @elrumordelaluz + http://elrumordelaluz.github.io/csshake/ + Licensed under MIT + \* * * * * * * * * * * * * * * * * * * * */ + .shake { + display: inherit; + transform-origin: center center; } + + .shake-freeze, + .shake-constant.shake-constant--hover:hover, + .shake-trigger:hover .shake-constant.shake-constant--hover { + animation-play-state: paused; } + + .shake-freeze:hover, + .shake-trigger:hover .shake-freeze, .shake:hover, + .shake-trigger:hover .shake { + animation-play-state: running; } + + @keyframes shake { + 2% { + transform: translate(-0.5px, 2.5px) rotate(-0.5deg); } + 4% { + transform: translate(0.5px, -1.5px) rotate(-0.5deg); } + 6% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 8% { + transform: translate(0.5px, 2.5px) rotate(-0.5deg); } + 10% { + transform: translate(-1.5px, 2.5px) rotate(1.5deg); } + 12% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 14% { + transform: translate(2.5px, 2.5px) rotate(1.5deg); } + 16% { + transform: translate(1.5px, 1.5px) rotate(0.5deg); } + 18% { + transform: translate(0.5px, 2.5px) rotate(1.5deg); } + 20% { + transform: translate(1.5px, -1.5px) rotate(-0.5deg); } + 22% { + transform: translate(0.5px, 1.5px) rotate(1.5deg); } + 24% { + transform: translate(-0.5px, 1.5px) rotate(0.5deg); } + 26% { + transform: translate(-1.5px, -1.5px) rotate(0.5deg); } + 28% { + transform: translate(1.5px, 2.5px) rotate(1.5deg); } + 30% { + transform: translate(2.5px, -1.5px) rotate(1.5deg); } + 32% { + transform: translate(1.5px, 1.5px) rotate(1.5deg); } + 34% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 36% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 38% { + transform: translate(0.5px, 0.5px) rotate(0.5deg); } + 40% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 42% { + transform: translate(0.5px, -0.5px) rotate(0.5deg); } + 44% { + transform: translate(-0.5px, -0.5px) rotate(-0.5deg); } + 46% { + transform: translate(1.5px, 2.5px) rotate(-0.5deg); } + 48% { + transform: translate(2.5px, 2.5px) rotate(1.5deg); } + 50% { + transform: translate(0.5px, 2.5px) rotate(1.5deg); } + 52% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 54% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 56% { + transform: translate(1.5px, 1.5px) rotate(0.5deg); } + 58% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 60% { + transform: translate(-0.5px, 0.5px) rotate(-0.5deg); } + 62% { + transform: translate(2.5px, -1.5px) rotate(1.5deg); } + 64% { + transform: translate(2.5px, -0.5px) rotate(0.5deg); } + 66% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 68% { + transform: translate(-1.5px, -0.5px) rotate(0.5deg); } + 70% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 72% { + transform: translate(1.5px, 0.5px) rotate(1.5deg); } + 74% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 76% { + transform: translate(-1.5px, -0.5px) rotate(0.5deg); } + 78% { + transform: translate(-1.5px, -1.5px) rotate(-0.5deg); } + 80% { + transform: translate(-0.5px, -0.5px) rotate(0.5deg); } + 82% { + transform: translate(-0.5px, 1.5px) rotate(1.5deg); } + 84% { + transform: translate(-1.5px, 2.5px) rotate(-0.5deg); } + 86% { + transform: translate(-1.5px, -0.5px) rotate(-0.5deg); } + 88% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 90% { + transform: translate(2.5px, -1.5px) rotate(-0.5deg); } + 92% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 94% { + transform: translate(0.5px, 1.5px) rotate(-0.5deg); } + 96% { + transform: translate(1.5px, 2.5px) rotate(0.5deg); } + 98% { + transform: translate(2.5px, -0.5px) rotate(1.5deg); } + 0%, 100% { + transform: translate(0, 0) rotate(0); } } + + .shake:hover, + .shake-trigger:hover .shake, .shake.shake-freeze, .shake.shake-constant { + animation-name: shake; + animation-duration: 100ms; + animation-timing-function: ease-in-out; + animation-iteration-count: infinite; } + /*! * * * * * * * * * * * * * * * * * * * * + CSShake :: shake + v1.5.0 + CSS classes to move your DOM + (c) 2015 @elrumordelaluz + http://elrumordelaluz.github.io/csshake/ + Licensed under MIT + \* * * * * * * * * * * * * * * * * * * * */ + .shake { + display: inherit; + transform-origin: center center; } + + .shake-freeze, + .shake-constant.shake-constant--hover:hover, + .shake-trigger:hover .shake-constant.shake-constant--hover { + animation-play-state: paused; } + + .shake-freeze:hover, + .shake-trigger:hover .shake-freeze, .shake:hover, + .shake-trigger:hover .shake { + animation-play-state: running; } + + @keyframes shake { + 2% { + transform: translate(-0.5px, 2.5px) rotate(-0.5deg); } + 4% { + transform: translate(0.5px, -1.5px) rotate(-0.5deg); } + 6% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 8% { + transform: translate(0.5px, 2.5px) rotate(-0.5deg); } + 10% { + transform: translate(-1.5px, 2.5px) rotate(1.5deg); } + 12% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 14% { + transform: translate(2.5px, 2.5px) rotate(1.5deg); } + 16% { + transform: translate(1.5px, 1.5px) rotate(0.5deg); } + 18% { + transform: translate(0.5px, 2.5px) rotate(1.5deg); } + 20% { + transform: translate(1.5px, -1.5px) rotate(-0.5deg); } + 22% { + transform: translate(0.5px, 1.5px) rotate(1.5deg); } + 24% { + transform: translate(-0.5px, 1.5px) rotate(0.5deg); } + 26% { + transform: translate(-1.5px, -1.5px) rotate(0.5deg); } + 28% { + transform: translate(1.5px, 2.5px) rotate(1.5deg); } + 30% { + transform: translate(2.5px, -1.5px) rotate(1.5deg); } + 32% { + transform: translate(1.5px, 1.5px) rotate(1.5deg); } + 34% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 36% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 38% { + transform: translate(0.5px, 0.5px) rotate(0.5deg); } + 40% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 42% { + transform: translate(0.5px, -0.5px) rotate(0.5deg); } + 44% { + transform: translate(-0.5px, -0.5px) rotate(-0.5deg); } + 46% { + transform: translate(1.5px, 2.5px) rotate(-0.5deg); } + 48% { + transform: translate(2.5px, 2.5px) rotate(1.5deg); } + 50% { + transform: translate(0.5px, 2.5px) rotate(1.5deg); } + 52% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 54% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 56% { + transform: translate(1.5px, 1.5px) rotate(0.5deg); } + 58% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 60% { + transform: translate(-0.5px, 0.5px) rotate(-0.5deg); } + 62% { + transform: translate(2.5px, -1.5px) rotate(1.5deg); } + 64% { + transform: translate(2.5px, -0.5px) rotate(0.5deg); } + 66% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 68% { + transform: translate(-1.5px, -0.5px) rotate(0.5deg); } + 70% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 72% { + transform: translate(1.5px, 0.5px) rotate(1.5deg); } + 74% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 76% { + transform: translate(-1.5px, -0.5px) rotate(0.5deg); } + 78% { + transform: translate(-1.5px, -1.5px) rotate(-0.5deg); } + 80% { + transform: translate(-0.5px, -0.5px) rotate(0.5deg); } + 82% { + transform: translate(-0.5px, 1.5px) rotate(1.5deg); } + 84% { + transform: translate(-1.5px, 2.5px) rotate(-0.5deg); } + 86% { + transform: translate(-1.5px, -0.5px) rotate(-0.5deg); } + 88% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 90% { + transform: translate(2.5px, -1.5px) rotate(-0.5deg); } + 92% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 94% { + transform: translate(0.5px, 1.5px) rotate(-0.5deg); } + 96% { + transform: translate(1.5px, 2.5px) rotate(0.5deg); } + 98% { + transform: translate(2.5px, -0.5px) rotate(1.5deg); } + 0%, 100% { + transform: translate(0, 0) rotate(0); } } + + .shake:hover, + .shake-trigger:hover .shake, .shake.shake-freeze, .shake.shake-constant { + animation-name: shake; + animation-duration: 100ms; + animation-timing-function: ease-in-out; + animation-iteration-count: infinite; }" + Http.responseWithText body 200) + +[request] +GET / HTTP/1.1 +Host: HOST +Date: Sun, 08 Nov 2020 15:38:01 GMT +Content-Length: 0 +Accept-encoding: gzip + + + +[response] +HTTP/1.1 200 OK +Date: xxx, xx xxx xxxx xx:xx:xx xxx +Content-Type: text/plain +Content-Encoding: gzip +Access-Control-Allow-Origin: * // FSHARPONLY +access-control-allow-origin: * // OCAMLONLY +x-darklang-execution-id: 0123456789 +Server: nginx/1.16.1 // OCAMLONLY +Server: darklang // FSHARPONLY +Connection: keep-alive // OCAMLONLY +Vary: Accept-Encoding // FSHARPONLY +Strict-Transport-Security: max-age=31536000; includeSubDomains; preload // FSHARPONLY +Transfer-Encoding: chunked + +/*! * * * * * * * * * * * * * * * * * * * * + CSShake :: shake + v1.5.0 + CSS classes to move your DOM + (c) 2015 @elrumordelaluz + http://elrumordelaluz.github.io/csshake/ + Licensed under MIT + \* * * * * * * * * * * * * * * * * * * * */ + .shake { + display: inherit; + transform-origin: center center; } + + .shake-freeze, + .shake-constant.shake-constant--hover:hover, + .shake-trigger:hover .shake-constant.shake-constant--hover { + animation-play-state: paused; } + + .shake-freeze:hover, + .shake-trigger:hover .shake-freeze, .shake:hover, + .shake-trigger:hover .shake { + animation-play-state: running; } + + @keyframes shake { + 2% { + transform: translate(-0.5px, 2.5px) rotate(-0.5deg); } + 4% { + transform: translate(0.5px, -1.5px) rotate(-0.5deg); } + 6% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 8% { + transform: translate(0.5px, 2.5px) rotate(-0.5deg); } + 10% { + transform: translate(-1.5px, 2.5px) rotate(1.5deg); } + 12% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 14% { + transform: translate(2.5px, 2.5px) rotate(1.5deg); } + 16% { + transform: translate(1.5px, 1.5px) rotate(0.5deg); } + 18% { + transform: translate(0.5px, 2.5px) rotate(1.5deg); } + 20% { + transform: translate(1.5px, -1.5px) rotate(-0.5deg); } + 22% { + transform: translate(0.5px, 1.5px) rotate(1.5deg); } + 24% { + transform: translate(-0.5px, 1.5px) rotate(0.5deg); } + 26% { + transform: translate(-1.5px, -1.5px) rotate(0.5deg); } + 28% { + transform: translate(1.5px, 2.5px) rotate(1.5deg); } + 30% { + transform: translate(2.5px, -1.5px) rotate(1.5deg); } + 32% { + transform: translate(1.5px, 1.5px) rotate(1.5deg); } + 34% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 36% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 38% { + transform: translate(0.5px, 0.5px) rotate(0.5deg); } + 40% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 42% { + transform: translate(0.5px, -0.5px) rotate(0.5deg); } + 44% { + transform: translate(-0.5px, -0.5px) rotate(-0.5deg); } + 46% { + transform: translate(1.5px, 2.5px) rotate(-0.5deg); } + 48% { + transform: translate(2.5px, 2.5px) rotate(1.5deg); } + 50% { + transform: translate(0.5px, 2.5px) rotate(1.5deg); } + 52% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 54% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 56% { + transform: translate(1.5px, 1.5px) rotate(0.5deg); } + 58% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 60% { + transform: translate(-0.5px, 0.5px) rotate(-0.5deg); } + 62% { + transform: translate(2.5px, -1.5px) rotate(1.5deg); } + 64% { + transform: translate(2.5px, -0.5px) rotate(0.5deg); } + 66% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 68% { + transform: translate(-1.5px, -0.5px) rotate(0.5deg); } + 70% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 72% { + transform: translate(1.5px, 0.5px) rotate(1.5deg); } + 74% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 76% { + transform: translate(-1.5px, -0.5px) rotate(0.5deg); } + 78% { + transform: translate(-1.5px, -1.5px) rotate(-0.5deg); } + 80% { + transform: translate(-0.5px, -0.5px) rotate(0.5deg); } + 82% { + transform: translate(-0.5px, 1.5px) rotate(1.5deg); } + 84% { + transform: translate(-1.5px, 2.5px) rotate(-0.5deg); } + 86% { + transform: translate(-1.5px, -0.5px) rotate(-0.5deg); } + 88% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 90% { + transform: translate(2.5px, -1.5px) rotate(-0.5deg); } + 92% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 94% { + transform: translate(0.5px, 1.5px) rotate(-0.5deg); } + 96% { + transform: translate(1.5px, 2.5px) rotate(0.5deg); } + 98% { + transform: translate(2.5px, -0.5px) rotate(1.5deg); } + 0%, 100% { + transform: translate(0, 0) rotate(0); } } + + .shake:hover, + .shake-trigger:hover .shake, .shake.shake-freeze, .shake.shake-constant { + animation-name: shake; + animation-duration: 100ms; + animation-timing-function: ease-in-out; + animation-iteration-count: infinite; } + /*! * * * * * * * * * * * * * * * * * * * * + CSShake :: shake + v1.5.0 + CSS classes to move your DOM + (c) 2015 @elrumordelaluz + http://elrumordelaluz.github.io/csshake/ + Licensed under MIT + \* * * * * * * * * * * * * * * * * * * * */ + .shake { + display: inherit; + transform-origin: center center; } + + .shake-freeze, + .shake-constant.shake-constant--hover:hover, + .shake-trigger:hover .shake-constant.shake-constant--hover { + animation-play-state: paused; } + + .shake-freeze:hover, + .shake-trigger:hover .shake-freeze, .shake:hover, + .shake-trigger:hover .shake { + animation-play-state: running; } + + @keyframes shake { + 2% { + transform: translate(-0.5px, 2.5px) rotate(-0.5deg); } + 4% { + transform: translate(0.5px, -1.5px) rotate(-0.5deg); } + 6% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 8% { + transform: translate(0.5px, 2.5px) rotate(-0.5deg); } + 10% { + transform: translate(-1.5px, 2.5px) rotate(1.5deg); } + 12% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 14% { + transform: translate(2.5px, 2.5px) rotate(1.5deg); } + 16% { + transform: translate(1.5px, 1.5px) rotate(0.5deg); } + 18% { + transform: translate(0.5px, 2.5px) rotate(1.5deg); } + 20% { + transform: translate(1.5px, -1.5px) rotate(-0.5deg); } + 22% { + transform: translate(0.5px, 1.5px) rotate(1.5deg); } + 24% { + transform: translate(-0.5px, 1.5px) rotate(0.5deg); } + 26% { + transform: translate(-1.5px, -1.5px) rotate(0.5deg); } + 28% { + transform: translate(1.5px, 2.5px) rotate(1.5deg); } + 30% { + transform: translate(2.5px, -1.5px) rotate(1.5deg); } + 32% { + transform: translate(1.5px, 1.5px) rotate(1.5deg); } + 34% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 36% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 38% { + transform: translate(0.5px, 0.5px) rotate(0.5deg); } + 40% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 42% { + transform: translate(0.5px, -0.5px) rotate(0.5deg); } + 44% { + transform: translate(-0.5px, -0.5px) rotate(-0.5deg); } + 46% { + transform: translate(1.5px, 2.5px) rotate(-0.5deg); } + 48% { + transform: translate(2.5px, 2.5px) rotate(1.5deg); } + 50% { + transform: translate(0.5px, 2.5px) rotate(1.5deg); } + 52% { + transform: translate(1.5px, -1.5px) rotate(1.5deg); } + 54% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 56% { + transform: translate(1.5px, 1.5px) rotate(0.5deg); } + 58% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 60% { + transform: translate(-0.5px, 0.5px) rotate(-0.5deg); } + 62% { + transform: translate(2.5px, -1.5px) rotate(1.5deg); } + 64% { + transform: translate(2.5px, -0.5px) rotate(0.5deg); } + 66% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 68% { + transform: translate(-1.5px, -0.5px) rotate(0.5deg); } + 70% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 72% { + transform: translate(1.5px, 0.5px) rotate(1.5deg); } + 74% { + transform: translate(0.5px, 0.5px) rotate(1.5deg); } + 76% { + transform: translate(-1.5px, -0.5px) rotate(0.5deg); } + 78% { + transform: translate(-1.5px, -1.5px) rotate(-0.5deg); } + 80% { + transform: translate(-0.5px, -0.5px) rotate(0.5deg); } + 82% { + transform: translate(-0.5px, 1.5px) rotate(1.5deg); } + 84% { + transform: translate(-1.5px, 2.5px) rotate(-0.5deg); } + 86% { + transform: translate(-1.5px, -0.5px) rotate(-0.5deg); } + 88% { + transform: translate(2.5px, -0.5px) rotate(-0.5deg); } + 90% { + transform: translate(2.5px, -1.5px) rotate(-0.5deg); } + 92% { + transform: translate(2.5px, 0.5px) rotate(-0.5deg); } + 94% { + transform: translate(0.5px, 1.5px) rotate(-0.5deg); } + 96% { + transform: translate(1.5px, 2.5px) rotate(0.5deg); } + 98% { + transform: translate(2.5px, -0.5px) rotate(1.5deg); } + 0%, 100% { + transform: translate(0, 0) rotate(0); } } + + .shake:hover, + .shake-trigger:hover .shake, .shake.shake-freeze, .shake.shake-constant { + animation-name: shake; + animation-duration: 100ms; + animation-timing-function: ease-in-out; + animation-iteration-count: infinite; } \ No newline at end of file diff --git a/fsharp-backend/tests/httptestfiles/request-header-content-encoding-br.test b/fsharp-backend/tests/httptestfiles/response-header-content-encoding-br.test similarity index 100% rename from fsharp-backend/tests/httptestfiles/request-header-content-encoding-br.test rename to fsharp-backend/tests/httptestfiles/response-header-content-encoding-br.test diff --git a/scripts/run-fsharp-tests b/scripts/run-fsharp-tests index 6a08b79a0c..e43cd5ca74 100755 --- a/scripts/run-fsharp-tests +++ b/scripts/run-fsharp-tests @@ -10,14 +10,14 @@ function ctrl_c() { exit 1 } -DEBUG=false +LLDB=false PUBLISHED=false for i in "$@" do case "${i}" in - --debug) - DEBUG=true + --lldb) + LLDB=true shift ;; --published) @@ -80,7 +80,7 @@ DARK_CONFIG_STATIC_HOST="static.darklang.localhost:${DARK_CONFIG_TEST_OCAMLSERVE JUNIT_FILE="${DARK_CONFIG_RUNDIR}/test_results/fsharp-backend.xml" cd fsharp-backend -if [[ "$DEBUG" == "true" ]]; then +if [[ "$LLDB" == "true" ]]; then DARK_CONFIG_TELEMETRY_EXPORTER=none \ DARK_CONFIG_ROLLBAR_ENABLED=n \ DARK_CONFIG_DB_HOST=localhost \