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 \