### Motivation:
As outlined in this
[issue](https://github.com/apple/swift-nio/issues/2930) by @weissi, the
current performance of calling `description` on `HTTPHeaders` is
undermined by the dynamism of Array.
### Modifications:
The proposed solution replaces the implementation of `description` to
iterate over the items and print them out manually. This provides a
faster solution since we bypass the cost of calling into `description`
of an Array.
### Result:
A more performant implementation of `HTTPHeaders` description.
---------
Co-authored-by: Johannes Weiss <johannesweiss@apple.com>
Co-authored-by: Cory Benfield <lukasa@apple.com>
* Apply formatting
* Apply no block comments rule
* Apply OmitExplicitReturns
* Apple OnlyOneTrailingClosureArgument
* Apply NoAssignmentInExpressions
* Fix up DontRepeatTypeInStaticProperties lint errors
* Apply `OrderedImports`
* Apply `ReplaceForEachWithForLoop`
* format file
* Enable the formatting pipeline
* Adopt `AmbiguousTrailingClosureOverload`
* Fix license header
* Fix format check
* Fix `EndOfLineComment`
* Fix CI
* Adapt CI script to check if changes when running formatting
* Separate lint and format into to steps
* Fix format
* Adopt `UseEarlyExits`
* Revert "Adopt `UseEarlyExits`"
This reverts commit d1ac5bbe12.
Motivation:
Follow up to https://github.com/apple/swift-nio/pull/1952#discussion_r707351015
The OWS rule from the HTTP semantics draft only considers 'SP' and
'HTAB' to be whitespace. We also (unnecessarily) consider CR and LF to
be whitespace.
Modifications:
- Remove CR and LF from the characters we consider to be whitespace
- Update tests
Result:
We no longer consider CR or LF to be whitespace when trimming
whitespace when producing the canonical form of header values.
Motivation:
As we've largely completed our move to split out our core abstractions,
we now have an opportunity to clean up our dependencies and imports. We
should arrange for everything to only import NIO if it actually needs
it, and to correctly express dependencies on NIOCore and NIOEmbedded
where they exist.
We aren't yet splitting out tests that only test functionality in
NIOCore, that will follow in a separate patch.
Modifications:
- Fixed up imports
- Made sure our protocols only require NIOCore.
Result:
Better expression of dependencies.
Co-authored-by: George Barnett <gbarnett@apple.com>
Motivation:
I'm sick of typing `.init(major: 1, minor: 1)`.
Modifications:
- Added static vars for common HTTP versions.
Result:
Maybe I'll never type `.init(major: 1, minor: 1)` ever again.
Motivation:
It's not always possible to initialize `HTTPHeaders` with all the headers one needs to set. In such case, the underlying array will be expanded as and when needed upon headers being appended. `Array` provides the means to reserve capacity to optimize such use case. This PR exposes this functionality in `HTTPHeaders`.
Modifications:
Expose method `reserveCapacity(_ minimumCapacity: Int)` and computed variable `capacity: Int` in `HTTPHeaders` and added some tests.
Result:
Developers will be able to control the capacity of the underlying headers array as such:
```
var headers = HTTPHeaders()
headers.reserveCapacity(5)
```
Co-authored-by: Ludovic Dewailly <ldewailly@apple.com>
Motivation:
In some cases you only expect (or care about) a single value for a
header. However, getting this value without allocating an array or
traversing the whole array requres using first(where:) provided by
Sequence. This is not very ergonomic as it requires users to do the
case-insensitive comparison and then exctact the value from the
HTTPHeaders. Additionally, users cannot use NIO's case insensitive
ASCII comparison.
Modifications:
- Provide a first(name:) method on HTTPHeaders which returns the value
from the first header whose name is a case insentive match of the
subscript parameter.
Result:
It's easier to get the first value from HTTPHeaders matching a given
name.
* Allow adding a sequence of headers to HTTPHeaders
Motivation:
When combining two collections of headers, adding each pair one-by-one
using add(name:value:) can cause multiple unnecessary reallocations of
the underlying array.
Modifications:
Provide two additional add methods to HTTPHeaders: one to add a sequence
of name/value pairs and another to add the headers from another
HTTPHeaders, both of which use `append(contentsOf:)` on the underlying
array to avoid additional reallocations.
Result:
Fewer reallocations when adding multiple headers.
* Fix incorrect documentation, make add<S: Sequence>(contentsOf:) @inlineable
* Reserve capacity and add sequentially instead of using append(contentsOf:)
* Add tests and missing @usableFromInline
Motivation:
EmbeddedChannel.finish/writeInbound/writeOutbound returned some mystery
bools. I always had to check the code to remember what they actually
meaned.
Whilst this is technically a breaking change, I couldn't find any users
of the return values on Github that are using the convergence releases.
Modifications:
Replace them by enums giving you all the information.
Result:
- fixes#916
- clearer APIs
Motivation:
The HTTP/1 headers were quite complicated, CoW-boxed and exposed their
guts (being a ByteBuffer). In Swift 5 this is no longer necessary
because of native UTF-8 Strings.
Modifications:
- make headers simply `[(String, String)]`
- remove `HTTPHeaderIndex`, `HTTPListHeaderIterator`, etc
Result:
- simpler and more performant code
Motivation:
Use better protocol instead of Sequence since it doesn't do much. Inspired by #676
Modifications:
Conform HTTPHeaders to RandomAccessCollection
Result:
HTTPHeaders will have RandomAccessCollection functionalities.
### Motivation:
A convenience/shorthand for developers.
### Modifications:
Add conformance to ExpressibleByDictionaryLiteral along with implementation.
### Result:
The following is would then be legal in Vapor:
```
return HTTPResponse(status: .ok, headers: ["Content-Type": "text/html"],
body: "<table border=1><tr><td><i>Hello</i>, plugin!")
```
Motivation:
ByteBuffer methods like `set(string:)` never felt very Swift-like and
also didn't look the same as their counterparts like `getString(...)`.
Modifications:
- rename all `ByteBuffer.set/write(<type>:,...)` methods to
`ByteBuffer.set/write<Type>(...)`
- polyfill the old spellings in `_NIO1APIShims`
Result:
code more Swift-like
Motivation:
NIO2 development starts now.
Modifications:
Made NIO Swift 5-only for everything else see docs/public-api-changes-NIO1-to-NIO2.md
Result:
NIO2 development can start.
Motivation:
HTTP/2 adds an additional item to a header key/value pair:
its indexability. By default, headers can be inserted into
the HPACK header table index; sometimes this will be denied
(common case is 'content-length' header, which is rarely going
to be the same, and will just cause the table to purge more
often than is really necessary). Additionally, a header may be
marked as not only non-indexable but also non-rewritable by
proxies. HTTPHeaders provides nowhere to store this, so the
HPACKHeaders implementation referenced in
apple/swift-nio-http2#10 was created to add in that capability.
Now, given that we really want the HTTP version to be an
implementation detail, we want to keep the HPACK details hidden,
and would thus be using HTTPHeaders in client APIs. NIOHTTP2
already has a HTTP1-to-HTTP2 channel handler that translates
between the two. Thus it behooves us to have the means to copy
the actual key/value pairs between the two types without making
a round-trip from UTF-8 bytes to UTF-16 Strings. These changes
allow NIOHPACK or NIOHTTP2 to implement that round-trip internally.
Modifications:
- HTTPHeader and HTTPHeaderIndex types are now public.
- HTTPHeaders.buffer and HTTPHeaders.headers properties are now
public.
- A new static method, HTTPHeaders.createHeaderBlock(buffer:headers:)
was created to serve as a public means to create a HTTPHeaders from
a ByteBuffer from outside NIOHTTP1. @Lukasa suggested this should
be a static method rather than an initializer.
Result:
Nothing in NIOHTTP1 changes in operation. All public types have
documentation comments noting that they are only public for very
specific reasons, and are not intended for general use. Once this
is committed, NIOHPACK & NIOHTTP2 will be able to implement fast
round-trips between HTTPHeaders and HPACKHeaders.
match all values in HTTPHeaders.isKeepAlive(...)
HTTPHeaders.isKeepAlive(...) does only match the first value.
### Motivation:
Keep-alive and Close may be something on comma separated arrays
### Modifications:
- Made an extension function to `ByteBuffer` that could separate the strings at low level, without Swift string API, so that it could split the values
- Another extension functions that compares the comma separated array with a given array to tell the caller which exists and which no
- Added unit tests for them
- Used them in the HTTPHeader
### Result:
- Now, if a request is sent using Keep-alive or Close where it was in an array, it will be handled
- fixes#410
Motivation:
To detect if keepalive is used we need to search the headers (if HTTP/1.1 is used). This may be expensive depending how many headers are present. http_parser itself detects already if keep alive should be used and so we can re-use this as long as the user did not modify the headers.
Modifications:
Reuse keepalive parsed by http_parser as long as the headers were not modified.
Result:
Less overhead as long as the headers are not modified.
Motivation:
Its very likely that the response will be HTTP/1.1 or HTTP/1.0 so we can optimize for it by minimizing the buffer.write(...) calls.
Beside this we should also change the pattern of *.write(into: inout ByteBuffer) to extension on ByteBuffer itself.
Modificiations:
- Optimize for HTTP/1.0 and 1.1
- Use extensions on ByteBuffer
Result:
Faster and more clean code.
Motivation:
We are currently parsing each header eagly which means we need to convert from bytes to String frequently. The reality is that most of the times the user is not really interested in all the headers and so it is kind of wasteful to do so.
Modification:
Rewrite our internal storage of HTTPHeaders to use a ByteBuffer as internal storage and so only parse headers on demand.
Result:
Less overhead for parsing headers.
Motivation:
HTTPHeaders had an unusual API for Swift: `getCanonicalForm(String)` which is
better suited as a subscript `[canonicalForm: String]`.
Also the implementation was needlessly inefficient.
Modifications:
- renamed HTTPHeaders.getCanonicalForm to HTTPHeaders[canonicalForm]
- improved efficiency by replacing anti-pattern
Array.map { ... }.reduce(+, []) by flatMap
Result:
more Swift in both meanings
Motivation:
For HTTP parsing, we used Foundation to trim whitespace which is not
needed.
Modifications:
Implemented whitespace trimming in Swift.
Result:
less Foundation
* Make HTTPHeaders iterate with originalcase'd names
Interface change as a consequence of this: now iterating headers
produces (name, value) tuples rather than (name, [values]).
* Add linux test wrapper
* Add case insensitive lookup test
* Update generate test headers
* Make HTTPHeadersIterator private