Giter Club home page Giter Club logo

swift-http-types's Introduction

Swift HTTP Types

Swift HTTP Types are version-independent HTTP currency types designed for both clients and servers. They provide a common set of representations for HTTP requests and responses, focusing on modern HTTP features.

Getting Started

Add the following dependency clause to your Package.swift:

dependencies: [
    .package(url: "https://github.com/apple/swift-http-types.git", from: "1.0.0")
]

The HTTPTypes library exposes the core HTTP currency types, including HTTPRequest, HTTPResponse, and HTTPFields.

The HTTPTypesFoundation library provides conveniences for using new HTTP types with Foundation, including bidirectional convertors between the new types and Foundation URL types, and URLSession convenience methods with the new types.

The NIOHTTPTypes, NIOHTTPTypesHTTP1, and NIOHTTPTypesHTTP2 libraries provide channel handlers for translating the version-specific NIO HTTP types with the new HTTP types. They can be found in swift-nio-extras.

Usage

Create a request

let request = HTTPRequest(method: .get, scheme: "https", authority: "www.example.com", path: "/")

Create a request from a Foundation URL

var request = HTTPRequest(method: .get, url: URL(string: "https://www.example.com/")!)
request.method = .post
request.path = "/upload"

Create a response

let response = HTTPResponse(status: .ok)

Access and modify header fields

extension HTTPField.Name {
    static let myCustomHeader = Self("My-Custom-Header")!
}

// Set
request.headerFields[.userAgent] = "MyApp/1.0"
request.headerFields[.myCustomHeader] = "custom-value"
request.headerFields[values: .acceptLanguage] = ["en-US", "zh-Hans-CN"]

// Get
request.headerFields[.userAgent] // "MyApp/1.0"
request.headerFields[.myCustomHeader] // "custom-value"
request.headerFields[.acceptLanguage] // "en-US, zh-Hans-CN"
request.headerFields[values: .acceptLanguage] // ["en-US", "zh-Hans-CN"]

Use with URLSession

var request = HTTPRequest(method: .post, url: URL(string: "https://www.example.com/upload")!)
request.headerFields[.userAgent] = "MyApp/1.0"
let (responseBody, response) = try await URLSession.shared.upload(for: request, from: requestBody)
guard response.status == .created else {
    // Handle error
}

Use with SwiftNIO

channel.configureHTTP2Pipeline(mode: .server) { channel in
    channel.pipeline.addHandlers([
        HTTP2FramePayloadToHTTPServerCodec(),
        ExampleChannelHandler()
    ])
}.map { _ in () }
final class ExampleChannelHandler: ChannelDuplexHandler {
    typealias InboundIn = HTTPTypeServerRequestPart
    typealias OutboundOut = HTTPTypeServerResponsePart

    func channelRead(context: ChannelHandlerContext, data: NIOAny) {
        switch unwrapInboundIn(data) {
        case .head(let request):
            // Handle request headers
        case .body(let body):
            // Handle request body
        case .end(let trailers):
            // Handle complete request
            let response = HTTPResponse(status: .ok)
            context.write(wrapOutboundOut(.head(response)), promise: nil)
            context.writeAndFlush(wrapOutboundOut(.end(nil)), promise: nil)
        }
    }
}

Developing HTTP Types

For the most part, HTTP Types development is as straightforward as any other SwiftPM project. With that said, we do have a few processes that are worth understanding before you contribute. For details, please see CONTRIBUTING.md in this repository.

Please note that all work on HTTP Types is covered by the Swift HTTP Types Code of Conduct.

swift-http-types's People

Contributors

0xtim avatar ehaydenr avatar es-kumagai avatar franzbusch avatar guoye-zhang avatar gwynne avatar jager-yoo avatar joannis avatar maxdesiatov avatar simonjbeaumont avatar zunda-pixel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

swift-http-types's Issues

More convenient APIs for headers

Vapor supports some nice hard-typed APIs that let users interact with some header fields in a convenient manner.
For example, consider the following:

import Vapor /// `Vapor` exports `NIOHTTP1`

var headers = NIOHTTP1.HTTPHeaders()
headers.contentType = HTTPMediaType.json 

This feature requests asks if such a feature can be supported by swift-http-types itself, instead of us having to implement it in each of HTTP-server packages such as Hummingbird (v2 is WIP and uses swift-http-types) or Vapor (in v5, the work has not yet begun but it's clear we'll be using swift-http-types).

My understanding is that a package like this would want to be very conservative about adding a feature like this, but I still think such convenient APIs are very valuable.

As i see it, one of the followings should be the case about this feature request, so I'm interested to know which one:

  • In scope, can be supported.
  • Not a main feature, maybe it can be in an e.g. HTTPTypesExtras target, or package.
  • Completely out of scope, will not be supported.

This is how vapor implements such a feature for the content-type header.
Note that content-type is just an example. There could be many more header field names supported with an API like this.

Should order matter when comparing `HTTPFields` for equality?

Currently order matters when comparing if two HTTPFields instances are equal.
I'm by no means an expert in HTTP standards but I found this surprising as the interface is very similar to that of a Dictionary.

My use case is I want to verify a HTTPRequest is as expected in tests but currently the test fails due to the headers having different ordering.

Should order matter here?

    static func testHeadersSameOrder() -> Bool {
        var headers1: HTTPFields = [:]
        headers1[.contentType] =  "application/json"
        headers1[.acceptLanguage] = "fr-CA"
        
        var headers2: HTTPFields = [:]
        headers2[.contentType] =  "application/json"
        headers2[.acceptLanguage] = "fr-CA"
        
        // returns true
        return headers1 == headers2
    }
    
    static func testHeadersDifferentOrder() -> Bool {
        var headers1: HTTPFields = [:]
        headers1[.acceptLanguage] = "fr-CA"
        headers1[.contentType] =  "application/json"
        
        var headers2: HTTPFields = [:]
        headers2[.contentType] =  "application/json"
        headers2[.acceptLanguage] = "fr-CA"
        
        // returns false
        return headers1 == headers2
    }

Invalid url

HTTPRequest always adds a / after the host, sometimes leading to invalid URLs.

let request = HTTPRequest(url:  URL(string: "https://example.com?page=some")!)
XCTAssertEqual(request.url!.absoluteString, "https://example.com?page=some")
// XCTAssertEqual failed: ("https://example.com/?page=some") is not equal to ("https://example.com?page=some")

I'm not sure if "https://example.com/?page=some" is an invalid URL, but I have experienced some servers returning errors for requests with "/?".
There is also another unexpected behavior:

let request = HTTPRequest(url:  URL(string: "https://example.com")!)
XCTAssertEqual(request.url!.absoluteString, "https://example.com")
// XCTAssertEqual failed: ("https://example.com/") is not equal to ("https://example.com")

I would not expect my URL to be altered in any way by HTTPRequest.

How do you handle query string in the path

Hello, it looks like this package is achieving mainly http2-like structures. In my case I use query string and I am having hard time to use this package along.

Am I using it wrong? How can I easily add a param to the query part of a URL of an HTTPRequest?

Thanks in advance for answer.

Missing built-in field names

This is the list of standards where the names came from, and ones that are currently missing:

  • RFC 2068: Hypertext Transfer Protocol -- HTTP/1.1
  • RFC 6265: HTTP State Management Mechanism
  • RFC 6266: Use of the Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)
  • RFC 6454: The Web Origin Concept
  • RFC 6455: The WebSocket Protocol
  • RFC 6797: HTTP Strict Transport Security (HSTS)
  • RFC 7838: HTTP Alternative Services
    Alt-Used
  • RFC 9110: HTTP Semantics
    Allow
    Content-Language
    Content-Location
    Content-Range
    From
    If-Match
    If-Unmodified-Since
    Max-Forwards
    Retry-After
    TE
  • RFC 9111: HTTP Caching
  • RFC 9218: Extensible Prioritization Scheme for HTTP
  • Content Security Policy Level 3
    Content-Security-Policy-Report-Only
  • Fetch
    Cross-Origin-Resource-Policy
    Sec-Purpose
  • Upgrade Insecure Requests

Let's evaluate which of these are useful to include (and which ones we can drop).

Add unsafe initialisers for HTTPField and HTTPField.Name

Currently when constructing an HTTPField or HTTPField.Name the values are checked to see if they are valid. This is the correct thing to do obviously when the source of the headers is unknown, but if we know in advance the headers are valid this is wasted effort.

The situation I am describing comes from building a SwiftNIO server which has constructed the header values via LLHTTP which validates they are correct and then we build a HTTPField from these that does the validation again.

Is there an argument for providing an unsafe initialiser for both HTTPField and HTTPField.Name to avoid this duplicate work

Conversion from relative HTTPRequest to absolute Foundation URL

Hello,

This issue is a feature request for a new API in the HTTPTypesFoundation module:

extension HTTPRequest {
    public func url(baseURL: URL) -> URL?
}

I met a need for this API while using http://github.com/apple/swift-openapi-generator

When writing a ClientMiddleware that processes http requests performed through URLSession, I need an absolute Foundation.URL in order to use various services such as HTTPCookieStorage.

This package defines a HTTPRequest.url property that looks very promising.

But the HTTPRequest I get from OpenAPIRuntime is not absolute. It is relative to a base URL, and its url property is nil:

struct MyMiddleware: ClientMiddleware {    
    func intercept(
        _ request: HTTPRequest,
        body requestBody: HTTPBody?,
        baseURL: URL,
        operationID: String,
        next: (HTTPRequest, HTTPBody?, URL) async throws -> (HTTPResponse, HTTPBody?)
    ) async throws -> (HTTPResponse, HTTPBody?)
    {
        print(request.url) // nil
    }
}

Since my requests are performed through URLSession, I was pretty sure to find some code that turns a (request, baseURL) pair into an absolute URL, suitable for URLRequest. Indeed, it is there.

This code is not public, so I defined my own version, inspired from the above implementation:

extension HTTPRequest {
    /// Returns an absolute URL.
    ///
    /// Inspired from <https://github.com/apple/swift-openapi-urlsession/blob/0.3.0/Sources/OpenAPIURLSession/URLSessionTransport.swift#L185-L208>
    func url(baseURL: URL) -> URL? {
        guard
            var baseUrlComponents = URLComponents(string: baseURL.absoluteString),
            let requestUrlComponents = URLComponents(string: path ?? "")
        else {
            return nil
        }

        let path = requestUrlComponents.percentEncodedPath
        baseUrlComponents.percentEncodedPath += path
        baseUrlComponents.percentEncodedQuery = requestUrlComponents.percentEncodedQuery
        return baseUrlComponents.url
    }
}

Since the implementation is not quite trivial, I believe it is a good candidate for inclusion in HTTPTypesFoundation.

What do you think?

Drop CF dependency

I noticed that this library uses CoreFoundation directly. IIRC that is not portable; CF is available on Darwin platforms and Linux, but is deliberately omitted from other platforms (e.g. Windows).

CoreFoundation is not meant to be public, and the Windows port has generally been far more aggressive about preventing leakage of internal interfaces and encouraging cross-compilation.

See discussion on the Swift forums announcement thread. Basically, Foundation doesn't (yet) provide the APIs this library needs. There may be ways around it, but that discussion is probably better to have here than on the forums.

`HTTPFields` raw subscript behavior question

In current implementation (0.2.1), after you set a combined field value via the plain subscript method, you will get the combined value from raw subscript method too:

var fields = HTTPFields()
fields[.acceptLanguage] = "en-US, zh-Hans-CN"
print(fields[.acceptLanguage]!) // en-US, zh-Hans-CN
print(fields[raw: .acceptLanguage]) // ["en-US, zh-Hans-CN"]

But I expect the raw subscript method will return ["en-US", "zh-Hans-CN"] in the above example. Is current behavior expected? Or it is a bug?

More consistent naming for HTTPFields subscripts

HTTPFields currently have 3 subscripts: subscript(_: HTTPField.Name) -> String?, subscript(raw: HTTPField.Name) -> [String], and subscript(fields: HTTPField.Name) -> [HTTPField] .

  • The first one subscript(_: HTTPField.Name) -> String? is equivalent to Foundation URLRequest/HTTPURLResponse value(forHTTPHeaderField:).
  • The second one subscript(raw: HTTPField.Name) -> [String] is equivalent to NIO HTTPHeaders.subscript(_:).
  • The third one has no equivalence in previous APIs since HTTPField is a new abstraction.
  • There is no equivalence to NIO HTTPHeaders.subscript(canonicalForm:).

During past discussions, it's been brought up that raw is a weird name, and "fields" is more "raw" than raw. Let's figure out if there is a better set of names for these subscripts.

Consider adding metadata property

There are many use cases where you may want to store some metadata on the request or even response (for example storing a request ID or a trace ID for distributed systems). Currently there's no way to do this, without adding an additional header which may not be desirable, or a pseudo header which feels like it's working around the problem rather than solving it.

Would a dictionary property or similar be something the team would consider?

How to set query string?

I don't know how to set a query string like URLQueryItem.
e.g. ?zipcode=2790031 in http://zipcloud.ibsnet.co.jp/api/search?zipcode=2790031

X-XSS-Protection upgrade option possible ?

Steps To Reproduce:
Perform any action and capture the HTTP Request.(iOS mobile apps with swift code)
Observe that in response X-XSS-Protection header was used.
HTTP/2
200
OK
Server :jag
Etag: 3655723661770096640-4617501349922598127
Content-Type:text/html;charset=utf-8
Content-Length:1
X-Xss-Protection: 1; mode=block

Recommendation:
Disable use of deprecated headers such as X-XSS-Protection.

References
X-XSS-Protection - HTTP | MDN

Impact
Even though this feature can protect users of older web browsers that don't yet support Content-Security-Policy, in some cases, XSS protection can create XSS vulnerabilities in otherwise safe websites.

Build fails for Swift 5.7.0

Based on swift-http-types's Package.swift this package support Swift 5.7.0+. However if I try to build it using Swift 5.7.0 it fails. Swift 5.7.1 works however.

See Swift 5.7.0 logs:

➜  swift-web-push git:(ff-fix-concurrency-problem) ✗ docker run -v $(pwd):/src -w /src -it swift:5.7.0-jammy /bin/bash
Unable to find image 'swift:5.7.0-jammy' locally
5.7.0-jammy: Pulling from library/swift
56a2caa6b2c6: Pull complete 
5f5781394e74: Pull complete 
4c758f5def4e: Pull complete 
cec232b6c8e8: Pull complete 
Digest: sha256:473744e71c112bb20fe897a3e0d985a6cb8aed98b6a9ae1461df5cf36214cb11
Status: Downloaded newer image for swift:5.7.0-jammy
root@8486157fd10c:/src# swift build
[1/1] Planning buildCompiling plugin MakeVapidKeyPlugin...
Compiling plugin Swift-DocC...
Compiling plugin Swift-DocC Preview...
Building for debugging...
/src/.build/checkouts/swift-http-types/Sources/HTTPTypes/HTTPFields.swift:165:68: error: cannot convert value of type 'String' to expected argument type 'String.Element' (aka 'Character')
                    self[fields: name] = newValue.split(separator: "; ", omittingEmptySubsequences: false).map {
                                                                   ^
/src/.build/checkouts/swift-http-types/Sources/HTTPTypes/HTTPFields.swift:165:68: error: cannot convert value of type 'String' to expected argument type 'String.Element' (aka 'Character')
                    self[fields: name] = newValue.split(separator: "; ", omittingEmptySubsequences: false).map {
                                                                   ^
[33/310] Compiling x_x509.c

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.