From 931c6abb4b8ba032e68ec200ed60d7b1658e644b Mon Sep 17 00:00:00 2001 From: Easher Date: Mon, 17 Jul 2017 16:39:23 +0800 Subject: [PATCH 1/2] readfile --- Sources/HttpParser.swift | 22 +++++++++++++++++++++- Sources/HttpRequest.swift | 7 +++++++ Sources/HttpServerIO.swift | 1 + Sources/Socket.swift | 11 +++++++++++ Sources/String+File.swift | 11 +++++++++++ 5 files changed, 51 insertions(+), 1 deletion(-) diff --git a/Sources/HttpParser.swift b/Sources/HttpParser.swift index ae7d0982..59f8963b 100644 --- a/Sources/HttpParser.swift +++ b/Sources/HttpParser.swift @@ -27,7 +27,11 @@ public class HttpParser { request.queryParams = extractQueryParams(request.path) request.headers = try readHeaders(socket) if let contentLength = request.headers["content-length"], let contentLengthValue = Int(contentLength) { - request.body = try readBody(socket, size: contentLengthValue) + if request.headers["content-type"] == "application/octet-stream" { + request.tempFile = try readFile(socket, length: contentLengthValue) + } else { + request.body = try readBody(socket, size: contentLengthValue) + } } return request } @@ -75,6 +79,22 @@ public class HttpParser { // } } + private func readFile(_ socket: Socket, length: Int) throws -> String { + var offset = 0 + let bufferLength = 1024 + let filePath = NSTemporaryDirectory() + "/" + NSUUID().uuidString + let file = try filePath.openNewForWriting() + + while offset < length { + let length = offset + bufferLength < length ? bufferLength : length - offset + let result = try socket.read(length: length) + try file.write(result.buffer, count: result.count) + offset += result.count + } + file.close() + return filePath + } + private func readBody(_ socket: Socket, size: Int) throws -> [UInt8] { var body = [UInt8]() for _ in 0.. Bool { guard let headerValue = headers[headerName] else { return false diff --git a/Sources/HttpServerIO.swift b/Sources/HttpServerIO.swift index 8e58e1bb..54492ae8 100644 --- a/Sources/HttpServerIO.swift +++ b/Sources/HttpServerIO.swift @@ -122,6 +122,7 @@ public class HttpServerIO { let (params, handler) = self.dispatch(request) request.params = params let response = handler(request) + try? request.removeTempFileIfExists() var keepConnection = parser.supportsKeepAlive(request.headers) do { if self.operating { diff --git a/Sources/Socket.swift b/Sources/Socket.swift index 54a5f1e4..c58161f5 100644 --- a/Sources/Socket.swift +++ b/Sources/Socket.swift @@ -111,6 +111,17 @@ open class Socket: Hashable, Equatable { } } + public typealias SocketReadResult = (count: Int, buffer: [UInt8]) + + open func read(length: Int) throws -> SocketReadResult { + var buffer = [UInt8](repeating: 0, count: length) + let count = recv(self.socketFileDescriptor as Int32, &buffer, buffer.count, 0) + if count <= 0 { + throw SocketError.recvFailed(Errno.description()) + } + return (count, buffer) + } + open func read() throws -> UInt8 { var buffer = [UInt8](repeating: 0, count: 1) let next = recv(self.socketFileDescriptor as Int32, &buffer, Int(buffer.count), 0) diff --git a/Sources/String+File.swift b/Sources/String+File.swift index 5e680a34..051af51d 100644 --- a/Sources/String+File.swift +++ b/Sources/String+File.swift @@ -58,6 +58,17 @@ extension String { } } + public func write(_ data: [UInt8], count: Int) throws -> Void { + if count <= 0 || data.count <= 0 { + return + } + try data.withUnsafeBufferPointer { + if fwrite($0.baseAddress, 1, count, self.pointer) != count { + throw FileError.error(errno) + } + } + } + public static func currentWorkingDirectory() throws -> String { guard let path = getcwd(nil, 0) else { throw FileError.error(errno) From f0b0950d827ff4cdd0a8666d47bc338655857bbe Mon Sep 17 00:00:00 2001 From: Easher Date: Tue, 18 Jul 2017 21:01:09 +0800 Subject: [PATCH 2/2] Increase the speed of request processing 1. Increase the speed of request processing 2. file upload preprocess, to ensure that the transmission of large files when the memory security --- Sources/DemoServer.swift | 39 +++++++++++++++++++++++++++++ Sources/HttpParser.swift | 51 ++++++++++++++++++++++++++++---------- Sources/HttpRequest.swift | 1 + Sources/HttpServer.swift | 2 +- Sources/HttpServerIO.swift | 22 +++++++++++++++- Sources/Socket.swift | 10 +++++--- Sources/String+File.swift | 11 -------- 7 files changed, 106 insertions(+), 30 deletions(-) diff --git a/Sources/DemoServer.swift b/Sources/DemoServer.swift index e3ec153e..672e5194 100644 --- a/Sources/DemoServer.swift +++ b/Sources/DemoServer.swift @@ -101,6 +101,45 @@ public func demoServer(_ publicDir: String) -> HttpServer { return HttpResponse.ok(.html(response)) } + server.GET["/upload/logo"] = { r in + guard let resourceURL = Bundle.main.resourceURL else { + return .notFound + } + + let logoURL = resourceURL.appendingPathComponent("logo.png") + guard let exists = try? logoURL.path.exists(), true == exists else { + return .notFound + } + + guard let url = URL(string: "http://127.0.0.1:9080/upload/logo"), let body = try? Data(contentsOf: logoURL) else { + return .notFound + } + + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.httpBody = body + request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type") + guard let data = try? NSURLConnection.sendSynchronousRequest(request, returning: nil) else { + return .badRequest(.html("Failed to send data")) + } + return .raw(200, "OK", [:], { writter in + try writter.write(data) + }) + } + + server.filePreprocess = true + server.POST["/upload/logo"] = { r in + guard let path = r.tempFile else { + return .badRequest(.html("no file")) + } + guard let file = try? path.openForReading() else { + return .notFound + } + return .raw(200, "OK", [:], { writter in + try writter.write(file) + }) + } + server.GET["/login"] = scopes { html { head { diff --git a/Sources/HttpParser.swift b/Sources/HttpParser.swift index 59f8963b..5e75f591 100644 --- a/Sources/HttpParser.swift +++ b/Sources/HttpParser.swift @@ -26,13 +26,13 @@ public class HttpParser { request.path = statusLineTokens[1] request.queryParams = extractQueryParams(request.path) request.headers = try readHeaders(socket) - if let contentLength = request.headers["content-length"], let contentLengthValue = Int(contentLength) { - if request.headers["content-type"] == "application/octet-stream" { - request.tempFile = try readFile(socket, length: contentLengthValue) - } else { - request.body = try readBody(socket, size: contentLengthValue) - } - } +// if let contentLength = request.headers["content-length"], let contentLengthValue = Int(contentLength) { +// if request.headers["content-type"] == "application/octet-stream" { +// request.tempFile = try readFile(socket, length: contentLengthValue) +// } else { +// request.body = try readBody(socket, size: contentLengthValue) +// } +// } return request } @@ -79,17 +79,33 @@ public class HttpParser { // } } + public func readContent(_ socket: Socket, request: HttpRequest, filePreprocess: Bool) throws { + guard let contentType = request.headers["content-type"], + let contentLength = request.headers["content-length"], + let contentLengthValue = Int(contentLength) else { + return + } + let isFileUpload = contentType == "application/octet-stream" + if isFileUpload && filePreprocess { + request.tempFile = try readFile(socket, length: contentLengthValue) + } + else { + request.body = try readBody(socket, size: contentLengthValue) + } + } + + private let kBufferLength = 1024 + private func readFile(_ socket: Socket, length: Int) throws -> String { var offset = 0 - let bufferLength = 1024 let filePath = NSTemporaryDirectory() + "/" + NSUUID().uuidString let file = try filePath.openNewForWriting() while offset < length { - let length = offset + bufferLength < length ? bufferLength : length - offset - let result = try socket.read(length: length) - try file.write(result.buffer, count: result.count) - offset += result.count + let length = offset + kBufferLength < length ? kBufferLength : length - offset + let buffer = try socket.read(length: length) + try file.write(buffer) + offset += buffer.count } file.close() return filePath @@ -97,8 +113,17 @@ public class HttpParser { private func readBody(_ socket: Socket, size: Int) throws -> [UInt8] { var body = [UInt8]() - for _ in 0.. [String: String] { diff --git a/Sources/HttpRequest.swift b/Sources/HttpRequest.swift index 72526fd9..fb5dc83d 100644 --- a/Sources/HttpRequest.swift +++ b/Sources/HttpRequest.swift @@ -24,6 +24,7 @@ public class HttpRequest { if let path = tempFile, try path.exists() { try FileManager.default.removeItem(atPath: path) } + tempFile = nil } public func hasTokenForHeader(_ headerName: String, token: String) -> Bool { diff --git a/Sources/HttpServer.swift b/Sources/HttpServer.swift index 6e72a80c..1608db26 100644 --- a/Sources/HttpServer.swift +++ b/Sources/HttpServer.swift @@ -40,7 +40,7 @@ public class HttpServer: HttpServerIO { } public var routes: [String] { - return router.routes(); + return router.routes() } public var notFoundHandler: ((HttpRequest) -> HttpResponse)? diff --git a/Sources/HttpServerIO.swift b/Sources/HttpServerIO.swift index 54492ae8..1aac9559 100644 --- a/Sources/HttpServerIO.swift +++ b/Sources/HttpServerIO.swift @@ -53,6 +53,14 @@ public class HttpServerIO { /// It's only used when the server is started with `forceIPv4` option set to false. /// Otherwise, `listenAddressIPv4` will be used. public var listenAddressIPv6: String? + + /// Bool representation of whether the file upload is preprocessed. + /// `true` if the file upload requires preprocessing when `content-type` is + /// `application/octet-stream`. `HttpParser` will create a temp file(`tempFile`) in + /// `NSTemporaryDirectory()`, and is deleted after the request ends. + /// Together, `body` will be empty. + /// `false` otherwise. + public var filePreprocess: Bool = false private let queue = DispatchQueue(label: "swifter.httpserverio.clientsockets") @@ -119,10 +127,22 @@ public class HttpServerIO { while self.operating, let request = try? parser.readHttpRequest(socket) { let request = request request.address = try? socket.peername() + + do { + try parser.readContent(socket, request: request, filePreprocess: filePreprocess) + } catch { + print("Failed to read content: \(error)") + break + } + let (params, handler) = self.dispatch(request) request.params = params let response = handler(request) - try? request.removeTempFileIfExists() + + if filePreprocess { + try? request.removeTempFileIfExists() + } + var keepConnection = parser.supportsKeepAlive(request.headers) do { if self.operating { diff --git a/Sources/Socket.swift b/Sources/Socket.swift index c58161f5..38b14857 100644 --- a/Sources/Socket.swift +++ b/Sources/Socket.swift @@ -111,15 +111,17 @@ open class Socket: Hashable, Equatable { } } - public typealias SocketReadResult = (count: Int, buffer: [UInt8]) - - open func read(length: Int) throws -> SocketReadResult { + open func read(length: Int) throws -> [UInt8] { var buffer = [UInt8](repeating: 0, count: length) let count = recv(self.socketFileDescriptor as Int32, &buffer, buffer.count, 0) if count <= 0 { throw SocketError.recvFailed(Errno.description()) } - return (count, buffer) + + if count < length { + buffer.removeSubrange(count.. UInt8 { diff --git a/Sources/String+File.swift b/Sources/String+File.swift index 051af51d..5e680a34 100644 --- a/Sources/String+File.swift +++ b/Sources/String+File.swift @@ -58,17 +58,6 @@ extension String { } } - public func write(_ data: [UInt8], count: Int) throws -> Void { - if count <= 0 || data.count <= 0 { - return - } - try data.withUnsafeBufferPointer { - if fwrite($0.baseAddress, 1, count, self.pointer) != count { - throw FileError.error(errno) - } - } - } - public static func currentWorkingDirectory() throws -> String { guard let path = getcwd(nil, 0) else { throw FileError.error(errno)