Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions Sources/DemoServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
53 changes: 49 additions & 4 deletions Sources/HttpParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +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) {
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
}

Expand Down Expand Up @@ -75,10 +79,51 @@ 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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of or in addition to filePreprocess the data should go to disk is it's pretty big, so that the server doesn't run into memory exhaustion issues. The written file should be handed back to the caller instead of just closed, and it should be memory-mapped so that it can be efficiently processed by the caller.

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 filePath = NSTemporaryDirectory() + "/" + NSUUID().uuidString
let file = try filePath.openNewForWriting()

while offset < length {
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
}

private func readBody(_ socket: Socket, size: Int) throws -> [UInt8] {
var body = [UInt8]()
for _ in 0..<size { body.append(try socket.read()) }
var offset = 0
while offset < size {
let length = offset + kBufferLength < size ? kBufferLength : size - offset
let buffer = try socket.read(length: length)
body.append(contentsOf: buffer)
offset += buffer.count
}
return body

// for _ in 0..<size { body.append(try socket.read()) }
// return body
}

private func readHeaders(_ socket: Socket) throws -> [String: String] {
Expand Down
8 changes: 8 additions & 0 deletions Sources/HttpRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,17 @@ public class HttpRequest {
public var body: [UInt8] = []
public var address: String? = ""
public var params: [String: String] = [:]
public var tempFile: String?

public init() {}

public func removeTempFileIfExists() throws {
if let path = tempFile, try path.exists() {
try FileManager.default.removeItem(atPath: path)
}
tempFile = nil
}

public func hasTokenForHeader(_ headerName: String, token: String) -> Bool {
guard let headerValue = headers[headerName] else {
return false
Expand Down
2 changes: 1 addition & 1 deletion Sources/HttpServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class HttpServer: HttpServerIO {
}

public var routes: [String] {
return router.routes();
return router.routes()
}

public var notFoundHandler: ((HttpRequest) -> HttpResponse)?
Expand Down
21 changes: 21 additions & 0 deletions Sources/HttpServerIO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down Expand Up @@ -119,9 +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)

if filePreprocess {
try? request.removeTempFileIfExists()
}

var keepConnection = parser.supportsKeepAlive(request.headers)
do {
if self.operating {
Expand Down
13 changes: 13 additions & 0 deletions Sources/Socket.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,19 @@ open class Socket: Hashable, Equatable {
}
}

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())
}

if count < length {
buffer.removeSubrange(count..<length)
}
return 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)
Expand Down