Skip to content

Commit 4d6f149

Browse files
Changes to HTTPHeaders API to integrate with HTTP/2 HPACKHeaders API
The `HTTPHeaders` API now vends `withUnsafeBufferAndIndices(_:)` and a static factory method to create `HTTPHeaders` from a `ByteBuffer` and a list of `HTTPHeader` values. Tests have been added, and the contributor script manually updated.
1 parent 57c6724 commit 4d6f149

File tree

5 files changed

+109
-2
lines changed

5 files changed

+109
-2
lines changed

.mailmap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ Tomer Doron <tomerd@apple.com>
33
Max Moiseev <moiseev@apple.com>
44
Johannes Weiß <johannesweiss@apple.com>
55
Adam Nemecek <adamnemecek@gmail.com>
6+
Jim Dovey <jimdovey@mac.com> <jdovey@linkedin.com>
67
<bas@basbroek.nl> <BasThomas@users.noreply.github.com>
78
<daniel_dunbar@apple.com> <daniel@zuster.org>
89
<johannesweiss@apple.com> <github@tux4u.de>

CONTRIBUTORS.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ needs to be listed here.
2626
- Helge Heß <helge@alwaysrightinstitute.com>
2727
- Ian Partridge <i.partridge@uk.ibm.com>
2828
- Jason Toffaletti <toffaletti@gmail.com>
29+
- Jim Dovey <jimdovey@mac.com>
2930
- Johannes Weiß <johannesweiss@apple.com>
3031
- John Connolly <connoljo2@gmail.com>
3132
- Karim ElNaggar <karimfarid.naggar@gmail.com>

Sources/NIOHTTP1/HTTPTypes.swift

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -368,13 +368,19 @@ public struct HTTPResponseHead: Equatable {
368368
}
369369

370370
/// The Index for a header name or value that points into the underlying `ByteBuffer`.
371-
struct HTTPHeaderIndex {
371+
///
372+
/// - note: This is public to aid in the creation of supplemental HTTP libraries, e.g.
373+
/// NIOHTTP2 and NIOHPACK. It is not intended for general use.
374+
public struct HTTPHeaderIndex {
372375
let start: Int
373376
let length: Int
374377
}
375378

376379
/// Struct which holds name, value pairs.
377-
struct HTTPHeader {
380+
///
381+
/// - note: This is public to aid in the creation of supplemental HTTP libraries, e.g.
382+
/// NIOHTTP2 and NIOHPACK. It is not intended for general use.
383+
public struct HTTPHeader {
378384
let name: HTTPHeaderIndex
379385
let value: HTTPHeaderIndex
380386
}
@@ -494,6 +500,41 @@ public struct HTTPHeaders: CustomStringConvertible {
494500
}
495501
return headersArray.description
496502
}
503+
504+
/// Creates a header block from a pre-filled contiguous string buffer containing a
505+
/// UTF-8 encoded HTTP header block, along with a list of the locations of each
506+
/// name/value pair within the block.
507+
///
508+
/// - note: This is public to aid in the creation of supplemental HTTP libraries, e.g.
509+
/// NIOHTTP2 and NIOHPACK. It is not intended for general use.
510+
///
511+
/// - Parameters:
512+
/// - buffer: A buffer containing UTF-8 encoded HTTP headers.
513+
/// - headers: The locations within `buffer` of the name and value of each header.
514+
/// - Returns: A new `HTTPHeaders` using the provided buffer as storage.
515+
public static func createHeaderBlock(buffer: ByteBuffer, headers: [HTTPHeader]) -> HTTPHeaders {
516+
return HTTPHeaders(buffer: buffer, headers: headers, keepAliveState: KeepAliveState.unknown)
517+
}
518+
519+
520+
/// Provides access to raw UTF-8 storage of the headers in this header block, along with
521+
/// a list of the header strings' indices.
522+
///
523+
/// - note: This is public to aid in the creation of supplemental HTTP libraries, e.g.
524+
/// NIOHTTP2 and NIOHPACK. It is not intended for general use.
525+
///
526+
/// - parameters:
527+
/// - block: A block that will be provided UTF-8 header block information.
528+
/// - buf: A raw `ByteBuffer` containing potentially-contiguous sequences of UTF-8 encoded
529+
/// characters.
530+
/// - locations: An array of `HTTPHeader`s, each of which contains information on the location in
531+
/// the buffer of both a header's name and value.
532+
/// - contiguous: A `Bool` indicating whether the headers are stored contiguously, with no padding
533+
/// or orphaned data within the block. If this is `true`, then the buffer represents
534+
/// a HTTP/1 header block appropriately encoded for the wire.
535+
public func withUnsafeBufferAndIndices<R>(_ block: (_ buf: ByteBuffer, _ locations: [HTTPHeader], _ contiguous: Bool) throws -> R) rethrows -> R {
536+
return try block(self.buffer, self.headers, self.continuous)
537+
}
497538

498539
/// Constructor used by our decoder to construct headers without the need of converting bytes to string.
499540
init(buffer: ByteBuffer, headers: [HTTPHeader], keepAliveState: KeepAliveState) {

Tests/NIOHTTP1Tests/HTTPHeadersTest+XCTest.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ extension HTTPHeadersTest {
4141
("testKeepAliveStateHasClose", testKeepAliveStateHasClose),
4242
("testResolveNonContiguousHeaders", testResolveNonContiguousHeaders),
4343
("testStringBasedHTTPListHeaderIterator", testStringBasedHTTPListHeaderIterator),
44+
("testUnsafeBufferAccess", testUnsafeBufferAccess),
45+
("testCreateFromBufferAndLocations", testCreateFromBufferAndLocations),
4446
]
4547
}
4648
}

Tests/NIOHTTP1Tests/HTTPHeadersTest.swift

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,5 +232,67 @@ class HTTPHeadersTest : XCTestCase {
232232
XCTAssertEqual(String(decoding: currentToken!, as: UTF8.self), "close")
233233
currentToken = tokenSource.next()
234234
XCTAssertNil(currentToken)
235+
}
236+
237+
func testUnsafeBufferAccess() {
238+
let originalHeaders = [ ("X-Header", "1"),
239+
("X-SomeHeader", "3"),
240+
("X-Header", "2")]
241+
let originalHeadersString = "X-Header: 1\r\nX-SomeHeader: 3\r\nX-Header: 2\r\n"
242+
var headers1 = HTTPHeaders(originalHeaders)
243+
244+
// ensure we can access the underlying buffer and header locations
245+
headers1.withUnsafeBufferAndIndices { (buf, locations, contiguous) in
246+
XCTAssertTrue(contiguous)
247+
XCTAssertEqual(locations.count, 3)
248+
XCTAssertEqual(buf.readableBytes, originalHeadersString.utf8.count) // NB: String considers "\r\n" to be one character
249+
250+
let str = buf.getString(at: 0, length: buf.readableBytes)
251+
XCTAssertEqual(str, originalHeadersString)
252+
}
253+
254+
// remove a header
255+
headers1.remove(name: "X-SomeHeader")
256+
257+
// should no longer be contiguous
258+
headers1.withUnsafeBufferAndIndices { (_, _, contiguous) in
259+
XCTAssertFalse(contiguous)
260+
}
261+
}
262+
263+
func testCreateFromBufferAndLocations() {
264+
let originalHeaders = [ ("User-Agent", "1"),
265+
("host", "2"),
266+
("X-SOMETHING", "3"),
267+
("X-Something", "4"),
268+
("SET-COOKIE", "foo=bar"),
269+
("Set-Cookie", "buz=cux")]
270+
271+
// create our own buffer and location list
272+
var buf = ByteBufferAllocator().buffer(capacity: 128)
273+
var locations: [HTTPHeader] = []
274+
for (name, value) in originalHeaders {
275+
let nstart = buf.writerIndex
276+
buf.write(string: name)
277+
let nameLoc = HTTPHeaderIndex(start: nstart, length: buf.writerIndex - nstart)
278+
buf.write(string: ": ")
279+
280+
let vstart = buf.writerIndex
281+
buf.write(string: value)
282+
let valueLoc = HTTPHeaderIndex(start: vstart, length: buf.writerIndex - vstart)
283+
buf.write(string: "\r\n")
284+
285+
locations.append(HTTPHeader(name: nameLoc, value: valueLoc))
286+
}
287+
288+
// create HTTP headers
289+
let headers = HTTPHeaders.createHeaderBlock(buffer: buf, headers: locations)
290+
291+
// looking up headers value is case-insensitive
292+
XCTAssertEqual(["1"], headers["User-Agent"])
293+
XCTAssertEqual(["1"], headers["User-agent"])
294+
XCTAssertEqual(["2"], headers["Host"])
295+
XCTAssertEqual(["3", "4"], headers["X-Something"])
296+
XCTAssertEqual(["foo=bar", "buz=cux"], headers["set-cookie"])
235297
}
236298
}

0 commit comments

Comments
 (0)