1
0
mirror of https://github.com/apple/swift-nio-extras.git synced 2025-05-31 01:37:03 +08:00
swift-nio-extras/Sources/NIOResumableUpload/HTTPResumableUploadHandler.swift
Guoye Zhang fde9d65d2e
Import HTTP resumable upload sample code ()
Support HTTP resumable upload.

### Motivation:

Supporting HTTP resumable upload protocol defined in
https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-resumable-upload-05

* Interop version 3: iOS 17.0, macOS 14.0
* Interop version 5: iOS 18.0, macOS 15.0
* Interop version 6: iOS 18.1, macOS 15.1

### Modifications:

2 new public classes, `HTTPResumableUploadHandler` and
`HTTPResumableUploadContext`, and a few other supporting objects to
manage resumable uploads and translate them into regular uploads.

---------

Co-authored-by: Jonathan Flat <jflat@apple.com>
Co-authored-by: Cory Benfield <lukasa@apple.com>
2024-12-11 07:39:13 +00:00

173 lines
5.4 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2023-2024 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import NIOCore
import NIOHTTPTypes
/// A channel handler that translates resumable uploads into regular uploads, and passes through
/// other HTTP traffic.
public final class HTTPResumableUploadHandler: ChannelDuplexHandler {
public typealias InboundIn = HTTPRequestPart
public typealias InboundOut = Never
public typealias OutboundIn = Never
public typealias OutboundOut = HTTPResponsePart
var upload: HTTPResumableUpload? = nil
let createUpload: () -> HTTPResumableUpload
var shouldReset: Bool = false
private var context: ChannelHandlerContext?
private var eventLoop: EventLoop!
/// Create an `HTTPResumableUploadHandler` within a given `HTTPResumableUploadContext`.
/// - Parameters:
/// - context: The context for this upload handler.
/// Use the same context across upload handlers, as uploads can't resume across different contexts.
/// - channelConfigurator: A closure for configuring the child HTTP server channel.
public init(
context: HTTPResumableUploadContext,
channelConfigurator: @escaping (Channel) -> Void
) {
self.createUpload = {
HTTPResumableUpload(
context: context,
channelConfigurator: channelConfigurator
)
}
}
/// Create an `HTTPResumableUploadHandler` within a given `HTTPResumableUploadContext`.
/// - Parameters:
/// - context: The context for this upload handler.
/// Use the same context across upload handlers, as uploads can't resume across different contexts.
/// - handlers: Handlers to add to the child HTTP server channel.
public init(
context: HTTPResumableUploadContext,
handlers: [ChannelHandler] = []
) {
self.createUpload = {
HTTPResumableUpload(context: context) { channel in
if !handlers.isEmpty {
_ = channel.pipeline.addHandlers(handlers)
}
}
}
}
private func resetUpload(context: ChannelHandlerContext) {
if let existingUpload = self.upload {
existingUpload.end(handler: self, error: nil)
}
let upload = self.createUpload()
upload.scheduleOnEventLoop(self.eventLoop)
upload.attachUploadHandler(self, channel: context.channel)
self.upload = upload
self.shouldReset = false
}
public func handlerAdded(context: ChannelHandlerContext) {
self.context = context
self.eventLoop = context.eventLoop
self.resetUpload(context: context)
}
public func channelActive(context: ChannelHandlerContext) {
context.read()
}
public func channelInactive(context: ChannelHandlerContext) {
self.upload?.end(handler: self, error: nil)
}
public func channelRead(context: ChannelHandlerContext, data: NIOAny) {
if self.shouldReset {
self.resetUpload(context: context)
}
let part = self.unwrapInboundIn(data)
if case .end = part {
self.shouldReset = true
}
self.upload?.receive(handler: self, channel: context.channel, part: part)
}
public func channelReadComplete(context: ChannelHandlerContext) {
self.upload?.receiveComplete(handler: self)
}
public func channelWritabilityChanged(context: ChannelHandlerContext) {
self.upload?.writabilityChanged(handler: self)
}
public func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {}
public func errorCaught(context: ChannelHandlerContext, error: Error) {
self.upload?.end(handler: self, error: error)
}
public func read(context: ChannelHandlerContext) {
if self.shouldReset {
context.read()
}
}
}
// For `HTTPResumableUpload`.
extension HTTPResumableUploadHandler {
private func runInEventLoop(_ work: @escaping () -> Void) {
if self.eventLoop.inEventLoop {
work()
} else {
self.eventLoop.execute(work)
}
}
func write(_ part: HTTPResponsePart, promise: EventLoopPromise<Void>?) {
self.runInEventLoop {
self.context?.write(self.wrapOutboundOut(part), promise: promise)
}
}
func flush() {
self.runInEventLoop {
self.context?.flush()
}
}
func writeAndFlush(_ part: HTTPResponsePart, promise: EventLoopPromise<Void>?) {
self.runInEventLoop {
self.context?.writeAndFlush(self.wrapOutboundOut(part), promise: promise)
}
}
func read() {
self.runInEventLoop {
self.context?.read()
}
}
func close(mode: CloseMode, promise: EventLoopPromise<Void>?) {
self.runInEventLoop {
self.context?.close(mode: mode, promise: promise)
}
}
func detach() {
self.runInEventLoop {
self.context = nil
}
}
}