Repository navigation
Brotli compression #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
7511801
Vendor in brotli-1.2.0 source.
carlohamalainen ae1fcaa
Prototype Brotli compression.
carlohamalainen df4126c
s/carlohamalainen/starfederation
carlohamalainen 6faf1f9
Move .vscode/c_cpp_properties.json to .vscode/c_cpp_properties.sample…
carlohamalainen 66dd9f6
Remove incorrect comment.
carlohamalainen aed9239
Script to pull in just the required C sources from brotli
carlohamalainen 228b016
Move fault injection and the allocation counter behind a `-DDATASTAR_…
carlohamalainen 7a3e757
Make out of range parameters a compile error.
carlohamalainen 8d4112f
Adjust Brotli defaults, mirroring Go SDK values.
carlohamalainen 8869738
Use a pthread_mutex_t to protect the underlying BrotliEncoderState.
carlohamalainen c0b6811
Improve error handling of callback/encoder.
carlohamalainen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,7 @@ | ||
| /.lake | ||
|
|
||
| .vscode/c_cpp_properties.json | ||
|
|
||
| tmp/ | ||
|
|
||
| node_modules/ | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| // Copy to c_cpp_properties.json (gitignored) and set the toolchain include | ||
| // path to match lean-toolchain; `lean --print-prefix` prints the prefix. | ||
| { | ||
| "version": 4, | ||
| "configurations": [ | ||
| { | ||
| "name": "datastar-brotli", | ||
| "includePath": [ | ||
| "${env:HOME}/.elan/toolchains/leanprover--lean4---v4.34.1/include", | ||
| "${workspaceFolder}/datastar-brotli/vendor/brotli/brotli-1.2.0/c/include" | ||
| ], | ||
| "defines": ["NDEBUG", "DATASTAR_BROTLI_TESTING"], | ||
| "cStandard": "c11" | ||
| } | ||
| ] | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import Test.Unit | ||
| import Test.StdHttp | ||
|
|
||
| /-- Run by `lake test`. The `#guard`s of `Test.Unit` are checked as it compiles. -/ | ||
| def main : IO UInt32 := do | ||
| let mut failures := 0 | ||
| for (name, test) in stdHttpTests do | ||
| try | ||
| test | ||
| IO.println s!"datastar: {name}: ok" | ||
| catch e => | ||
| IO.eprintln s!"FAILED {name}: {e}" | ||
| failures := failures + 1 | ||
| return if failures == 0 then 0 else 1 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| import Datastar | ||
|
|
||
| /-! | ||
| The SSE response over a mock connection, run by `lake test`. | ||
|
|
||
| Checked: | ||
|
|
||
| * When the callback returns and `finish` fails, the response fails: the connection is dropped | ||
| without the chunked terminator, and `onFailure` sees the error. Ended normally, a compressed | ||
| stream would reach the client cut short, and nothing on the server would show it. | ||
| * When `finish` succeeds, its bytes are sent and the response ends normally. | ||
| * When the callback fails, that is the error reported, and a failing `finish` is then ignored. | ||
| -/ | ||
|
|
||
| open Std Async Http Server | ||
| open Datastar | ||
|
|
||
| /-- A compressor that passes bytes through, ending the stream with `finish`. -/ | ||
| private def passthrough (finish : IO ByteArray) : Compressor where | ||
| encoding := "test" | ||
| start := pure { compress := pure, finish } | ||
|
|
||
| private def finishFailure : IO ByteArray := throw (IO.userError "finish failed") | ||
|
|
||
| private def callbackFailure : ServerSentEventGenerator → ContextAsync Unit := | ||
| fun _ => throw (IO.userError "callback failed") | ||
|
|
||
| private def oneEvent (sse : ServerSentEventGenerator) : ContextAsync Unit := | ||
| sse.send <| patchSignals "{\"n\":1}" | ||
|
|
||
| private def request : String := | ||
| "GET /sse HTTP/1.1\r\nHost: example.com\r\nAccept-Encoding: test\r\nConnection: close\r\n\r\n" | ||
|
|
||
| /-- The chunked terminator, which ends a response that completed. -/ | ||
| private def chunkEnd : String := "0\r\n\r\n" | ||
|
|
||
| /-- Serve `request` to an SSE handler: the bytes written to the client, and the errors reported. -/ | ||
| private def serve (compressor : Compressor) (callback : ServerSentEventGenerator → ContextAsync Unit) : | ||
| IO (String × List String) := do | ||
| let failures ← IO.mkRef ([] : List String) | ||
| let handler := Handler.ofFns (fun req => sseResponseWith [compressor] req callback) | ||
| (onFailure := fun e => failures.modify (· ++ [toString e])) | ||
| let (client, server) ← Internal.Mock.new | ||
| let output ← Async.block do | ||
| client.send request.toUTF8 | ||
| (serveConnection server handler { lingeringTimeout := 1000, generateDate := false }).run | ||
| client.recv? | ||
| return (String.fromUTF8! (output.getD .empty), ← failures.get) | ||
|
|
||
| private def check (condition : Bool) (message : String) : IO Unit := | ||
| unless condition do throw <| IO.userError message | ||
|
|
||
| private def has (text needle : String) : Bool := (text.splitOn needle).length > 1 | ||
|
|
||
| private def finishFails : IO Unit := do | ||
| let (output, failures) ← serve (passthrough finishFailure) oneEvent | ||
| check (output.startsWith "HTTP/1.1 200") s!"response: {output.quote}" | ||
| check (has output "data: signals {\"n\":1}") s!"event not sent: {output.quote}" | ||
| check (!output.endsWith chunkEnd) s!"response ended as complete: {output.quote}" | ||
| check (failures == ["finish failed"]) s!"failures reported: {failures}" | ||
|
|
||
| private def finishSucceeds : IO Unit := do | ||
| let (output, failures) ← serve (passthrough (pure "tail".toUTF8)) oneEvent | ||
| check (has output "data: signals {\"n\":1}") s!"event not sent: {output.quote}" | ||
| check (output.endsWith s!"tail\r\n{chunkEnd}") s!"final bytes or terminator missing: {output.quote}" | ||
| check (failures == []) s!"failures reported: {failures}" | ||
|
|
||
| private def callbackFails : IO Unit := do | ||
| let (_, failures) ← serve (passthrough finishFailure) callbackFailure | ||
| check (failures == ["callback failed"]) s!"failures reported: {failures}" | ||
|
|
||
| /-- The tests of this module, by name. -/ | ||
| def stdHttpTests : List (String × IO Unit) := [ | ||
| ("finish fails after the callback returns", finishFails), | ||
| ("finish succeeds", finishSucceeds), | ||
| ("the callback fails", callbackFails)] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| /.lake |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| import Datastar | ||
| import DatastarBrotli | ||
|
|
||
| import Std.Async | ||
| import Std.Http | ||
| import Std.Net.Addr | ||
|
|
||
| /-! | ||
| Serves the page that the Playwright test in `e2e/tests/brotli.spec.ts` drives. | ||
|
|
||
| `/sse/stream` is a Brotli-compressed stream that stops after each event until the test calls | ||
| `/release`. The test can then see that the browser has an event while the stream is still open, | ||
| which is only true if every event is flushed through the encoder. | ||
| -/ | ||
|
|
||
| open Std Async Http Server | ||
| open Datastar | ||
|
|
||
| def testPage : String := include_str "e2e-server.html" | ||
|
|
||
| /-- A list of `count` items; 3000 of them are well over the encoder's 64 KiB input block. -/ | ||
| def largeList (count : Nat) : String := | ||
| let items := (List.range count).map fun i => s!"<li id=\"item-{i}\">Item number {i}</li>" | ||
| s!"<ul id=\"large\">{String.join items}</ul>" | ||
|
|
||
| /-- Wait until `/release` has been called `count` times. -/ | ||
| partial def waitForRelease (released : IO.Ref Nat) (count : Nat) : Async Unit := do | ||
| if (← released.get) < count then | ||
| sleep (.ofNat 20) | ||
| waitForRelease released count | ||
|
|
||
| def app (released : IO.Ref Nat) (req : Request Body.Stream) : ContextAsync (Response Body.Any) := do | ||
| match req.line.method, toString req.line.uri.path with | ||
| | .get, "/" => Response.ok |>.html testPage | ||
| | .get, "/release" => | ||
| released.modify (· + 1) | ||
| Response.ok |>.text "released" | ||
| | .get, "/sse/stream" => | ||
| let start ← released.get | ||
| sseResponseWith [brotli] req fun sse => do | ||
| sse.send <| patchElements "<div id=\"step\">Event 1</div>" | ||
| waitForRelease released (start + 1) | ||
| sse.send <| patchElements (largeList 3000) | ||
| sse.send <| patchElements "<div id=\"step\">Event 2</div>" | ||
| waitForRelease released (start + 2) | ||
| sse.send <| patchElements "<div id=\"step\">Event 3</div>" | ||
| | _, _ => Response.notFound |>.text "Not found" | ||
|
|
||
| def main : IO Unit := Async.block do | ||
| let addr : Std.Net.SocketAddressV4 := ⟨.ofParts 127 0 0 1, 3114⟩ | ||
| let released ← IO.mkRef 0 | ||
|
|
||
| let server ← Server.serve addr <| Handler.ofFn (app released) | ||
|
|
||
| IO.println "brotli-e2e-server running on http://127.0.0.1:3114" | ||
| (← IO.getStdout).flush | ||
| server.waitShutdown |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.