Skip to content

Add brotli support to FoundationNetworking #5251

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ if let environmentPath = Context.environment["CURL_INCLUDE_PATH"] {

var curlLinkFlags: [LinkerSetting] = [
.linkedLibrary("libcurl.lib", .when(platforms: [.windows])),
.linkedLibrary("zlibstatic.lib", .when(platforms: [.windows]))
.linkedLibrary("zlibstatic.lib", .when(platforms: [.windows])),
.linkedLibrary("brotlicommon.lib", .when(platforms: [.windows])),
.linkedLibrary("brotlidec.lib", .when(platforms: [.windows]))
]
if let environmentPath = Context.environment["CURL_LIBRARY_PATH"] {
curlLinkFlags.append(.unsafeFlags([
Expand All @@ -70,6 +72,11 @@ if let environmentPath = Context.environment["ZLIB_LIBRARY_PATH"] {
"-L\(environmentPath)"
]))
}
if let environmentPath = Context.environment["BROTLI_LIBRARY_PATH"] {
curlLinkFlags.append(.unsafeFlags([
"-L\(environmentPath)"
]))
}

var libxmlLinkFlags: [LinkerSetting] = [
.linkedLibrary("libxml2s.lib", .when(platforms: [.windows]))
Expand Down
24 changes: 24 additions & 0 deletions Tests/Foundation/HTTPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,18 @@ class _HTTPServer: CustomStringConvertible {
"\r\n").data(using: .utf8)!
try tcpSocket.writeRawData(responseData)
}

func respondWithAcceptEncoding(request: _HTTPRequest) throws {
var responseData: Data
if let acceptEncoding = request.getHeader(for: "Accept-Encoding") {
let content = acceptEncoding.data(using: .utf8)!
responseData = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=ISO-8859-1\r\nContent-Length: \(content.count)\r\n\r\n".data(using: .utf8)!
responseData.append(content)
} else {
responseData = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=ISO-8859-1\r\nContent-Length: 0\r\n\r\n".data(using: .utf8)!
}
try tcpSocket.writeRawData(responseData)
}
}

struct _HTTPRequest: CustomStringConvertible {
Expand Down Expand Up @@ -690,6 +702,8 @@ public class TestURLSessionServer: CustomStringConvertible {
try httpServer.respondWithUnauthorizedHeader()
} else if req.uri.hasPrefix("/web-socket") {
try handleWebSocketRequest(req)
} else if req.uri.hasPrefix("/accept-encoding") {
try httpServer.respondWithAcceptEncoding(request: req)
} else {
let response = try getResponse(request: req)
try httpServer.respond(with: response)
Expand Down Expand Up @@ -852,6 +866,16 @@ public class TestURLSessionServer: CustomStringConvertible {
"Content-Encoding: gzip"].joined(separator: _HTTPUtils.CRLF),
bodyData: helloWorld)
}

if uri == "/brotli-response" {
// This is "Hello World!" brotli encoded.
let helloWorld = Data([0x8B, 0x05, 0x80, 0x48, 0x65, 0x6C, 0x6C, 0x6F,
0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64, 0x21, 0x03])
return _HTTPResponse(response: .OK,
headers: ["Content-Length: \(helloWorld.count)",
"Content-Encoding: br"].joined(separator: _HTTPUtils.CRLF),
bodyData: helloWorld)
}

if uri == "/echo-query" {
let body = request.parameters.map { "\($0.key)=\($0.value)" }.joined(separator: "&")
Expand Down
23 changes: 23 additions & 0 deletions Tests/Foundation/TestURLSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ final class TestURLSession: LoopbackServerTest, @unchecked Sendable {
}
}

func test_dataTaskWithAcceptEncoding() async {
let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/accept-encoding"
let url = URL(string: urlString)!
let d = DataTask(with: expectation(description: "GET \(urlString): with a delegate"))
d.run(with: url)
waitForExpectations(timeout: 12)
if !d.error {
let supportedEncodings = d.capital.split(separator: ",").map { $0.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines ) }
XCTAssert(supportedEncodings.contains("br"), "test_dataTaskWithURLRequest returned an unexpected result")
}
}

func test_dataTaskWithURLCompletionHandler() async {
//shared session
await dataTaskWithURLCompletionHandler(with: URLSession.shared)
Expand Down Expand Up @@ -256,6 +268,17 @@ final class TestURLSession: LoopbackServerTest, @unchecked Sendable {
}
}

func test_brotliDataTask() async {
let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/brotli-response"
let url = URL(string: urlString)!
let d = DataTask(with: expectation(description: "GET \(urlString): brotli response"))
d.run(with: url)
waitForExpectations(timeout: 12)
if !d.error {
XCTAssertEqual(d.capital, "Hello World!")
}
}

func test_downloadTaskWithURL() async {
let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/country.txt"
let url = URL(string: urlString)!
Expand Down
31 changes: 29 additions & 2 deletions cmake/modules/WindowsSwiftPMDependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ function(_foundation_setup_windows_swiftpm_dependencies_target)
EXCLUDE_FROM_ALL YES
)

ExternalProject_Add(brotli
GIT_REPOSITORY https://github.com/google/brotli
GIT_TAG v1.1.0
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=${DEST_DIR}/brotli
-DCMAKE_C_COMPILER=cl
-DBUILD_SHARED_LIBS=NO
-DCMAKE_POSITION_INDEPENDENT_CODE=YES
-DCMAKE_BUILD_TYPE=Release
EXCLUDE_FROM_ALL YES
)

ExternalProject_Add(libxml
GIT_REPOSITORY https://github.com/gnome/libxml2.git
GIT_TAG v2.11.5
Expand All @@ -63,6 +75,15 @@ function(_foundation_setup_windows_swiftpm_dependencies_target)
# Add a custom target for zlib's install step that curl can depend on
ExternalProject_Add_StepTargets(zlib install)

set(BROTLI_ROOT "${DEST_DIR}/brotli")
set(BROTLI_LIBRARY_DIR "${BROTLI_ROOT}/lib")
set(BROTLI_INCLUDE_DIR "${BROTLI_ROOT}/include")
set(BROTLICOMMON_LIBRARY_PATH "${BROTLI_LIBRARY_DIR}/brotlicommon.lib")
set(BROTLIDEC_LIBRARY_PATH "${BROTLI_LIBRARY_DIR}/brotlidec.lib")

# Add a custom target for brotli's install step that curl can depend on
ExternalProject_Add_StepTargets(brotli install)

ExternalProject_Add(curl
GIT_REPOSITORY https://github.com/curl/curl.git
GIT_TAG curl-8_9_1
Expand All @@ -75,7 +96,7 @@ function(_foundation_setup_windows_swiftpm_dependencies_target)
-DCURL_CA_BUNDLE=none
-DCURL_CA_FALLBACK=NO
-DCURL_CA_PATH=none
-DCURL_BROTLI=NO
-DCURL_BROTLI=YES
-DCURL_DISABLE_ALTSVC=NO
-DCURL_DISABLE_AWS=YES
-DCURL_DISABLE_BASIC_AUTH=NO
Expand Down Expand Up @@ -150,7 +171,10 @@ function(_foundation_setup_windows_swiftpm_dependencies_target)
-DZLIB_ROOT=${ZLIB_ROOT}
-DZLIB_LIBRARY=${ZLIB_LIBRARY_PATH}
-DZLIB_INCLUDE_DIR=${ZLIB_INCLUDE_DIR}
DEPENDS zlib-install
-DBROTLIDEC_LIBRARY=${BROTLIDEC_LIBRARY_PATH}
-DBROTLICOMMON_LIBRARY=${BROTLICOMMON_LIBRARY_PATH}
-DBROTLI_INCLUDE_DIR=${BROTLI_INCLUDE_DIR}
DEPENDS zlib-install brotli-install
EXCLUDE_FROM_ALL YES
)

Expand All @@ -166,6 +190,7 @@ function(_foundation_setup_windows_swiftpm_dependencies_target)
message(STATUS "CURL_INCLUDE_PATH=${CURL_INCLUDE_DIR}")
message(STATUS "CURL_LIBRARY_PATH=${CURL_LIBRARY_DIR}")
message(STATUS "ZLIB_LIBRARY_PATH=${ZLIB_LIBRARY_DIR}")
message(STATUS "BROTLI_LIBRARY_PATH=${BROTLI_LIBRARY_DIR}")

ExternalProject_Add_StepTargets(libxml install)
ExternalProject_Add_StepTargets(curl install)
Expand All @@ -186,5 +211,7 @@ function(_foundation_setup_windows_swiftpm_dependencies_target)
COMMAND echo CURL_LIBRARY_PATH=${CURL_LIBRARY_DIR})
add_custom_command(TARGET WindowsSwiftPMDependencies POST_BUILD
COMMAND echo ZLIB_LIBRARY_PATH=${ZLIB_LIBRARY_DIR})
add_custom_command(TARGET WindowsSwiftPMDependencies POST_BUILD
COMMAND echo BROTLI_LIBRARY_PATH=${BROTLI_LIBRARY_DIR})

endfunction()