Skip to content

Commit

Permalink
add multipart + url-encoded content; cleanup old files (vapor#1947)
Browse files Browse the repository at this point in the history
* add multipart + url-encoded content; cleanup old files

* update some tests

* remove Vapor.Context, use Vapor.Request

* Request / Response refactor

* cleanup XCTTest cases

* shutdown cleanup / non-throwing

* update some tests; fix test manifest

* add validation

* finish Validation updates

* fix some warnings

* build -v

* console updates

* don't use invalidateAndCancel on linux
  • Loading branch information
tanner0101 authored Apr 10, 2019
1 parent b6c21bc commit af2b50e
Show file tree
Hide file tree
Showing 170 changed files with 11,156 additions and 3,027 deletions.
41 changes: 29 additions & 12 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,49 @@ let package = Package(
// 🔑 Hashing (BCrypt, SHA, HMAC, etc), encryption, and randomness.
.package(url: "https://github.com/vapor/crypto.git", .branch("master")),

// 🚀 Non-blocking, event-driven HTTP for Swift built on Swift NIO.
.package(url: "https://github.com/vapor/http.git", .branch("master")),

// 🚍 High-performance trie-node router.
.package(url: "https://github.com/vapor/routing.git", .branch("master")),

// 📦 Dependency injection / inversion of control framework.
.package(url: "https://github.com/vapor/service.git", .branch("master")),

// ✅ Extensible data validation library (email, alphanumeric, UUID, etc)
// .package(url: "https://github.com/vapor/validation.git", from: "2.0.0"),

// Event-driven network application framework for high performance protocol servers & clients, non-blocking.
.package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0"),

// Bindings to OpenSSL-compatible libraries for TLS support in SwiftNIO
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.0.0"),

// HTTP/2 support for SwiftNIO
.package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.0.0"),

// Useful code around SwiftNIO.
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.0.0"),

// Swift logging API
.package(url: "https://github.com/apple/swift-log.git", .branch("master")),
],
targets: [
.target(name: "CMultipartParser"),
.target(name: "COperatingSystem"),

// Boilerplate
.target(name: "Boilerplate", dependencies: ["Vapor"]),
.target(name: "BoilerplateRun", dependencies: ["Boilerplate"]),

// Vapor
.target(name: "Development", dependencies: ["Vapor"]),
.target(name: "Vapor", dependencies: [
"CMultipartParser",
"COperatingSystem",
"ConsoleKit",
"CryptoKit",
"HTTPKit",
"Logging",
"NIO",
"NIOExtras",
"NIOFoundationCompat",
"NIOHTTPCompression",
"NIOHTTP1",
"NIOHTTP2",
"NIOSSL",
"NIOWebSocket",
"RoutingKit",
"ServiceKit",
// "Validation",
]),
.target(name: "XCTVapor", dependencies: ["Vapor"]),
.testTarget(name: "VaporTests", dependencies: ["XCTVapor"]),
Expand Down
18 changes: 8 additions & 10 deletions Sources/Boilerplate/boot.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import Vapor

public func boot(_ app: Application) throws {
let test = try app.makeContainer().wait()
let routes = try test.make(Routes.self)
for route in routes.routes {
let path = route.path.map { $0.description }.joined(separator: "/")
print("[\(route.method)] /\(path) \(route.requestType) -> \(route.responseType)")
for (key, val) in route.userInfo {
print(" - \(key) = \(val)")
}
}
try test.shutdown().wait()
let c = try app.makeContainer().wait()
defer { c.shutdown() }

// bootstrap logging system
let console = try c.make(Console.self)
LoggingSystem.bootstrap(console: console, level: .info)

// use container
}
22 changes: 8 additions & 14 deletions Sources/Boilerplate/configure.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,21 @@ public func configure(_ s: inout Services) throws {
try routes(r, c)
}

s.register(HTTPServer.Configuration.self) { c in
s.register(ServerConfiguration.self) { c in
switch c.env {
case .tls:
return .init(
hostname: "127.0.0.1",
port: 8443,
tlsConfig: .forServer(
certificateChain: [.file("/Users/tanner0101/dev/vapor/net-kit/certs/cert.pem")],
privateKey: .file("/Users/tanner0101/dev/vapor/net-kit/certs/key.pem")
)
)
return .init(hostname: "127.0.0.1", port: 8443, tlsConfiguration: tls)
default:
return .init(
hostname: "127.0.0.1",
port: 8080,
supportVersions: [.one]
)
return .init(hostname: "127.0.0.1", port: 8080)
}
}
}

let tls = TLSConfiguration.forServer(
certificateChain: [.file("/Users/tanner0101/dev/vapor/net-kit/certs/cert.pem")],
privateKey: .file("/Users/tanner0101/dev/vapor/net-kit/certs/key.pem")
)

extension Environment {
static var tls: Environment {
return .custom(name: "tls")
Expand Down
78 changes: 42 additions & 36 deletions Sources/Boilerplate/routes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,38 @@ struct Creds: Content {
}

public func routes(_ r: Routes, _ c: Container) throws {
r.get("ping") { _, _ in
r.on(.GET, "ping", body: .stream) { req in
return "123" as StaticString
}

r.post("login") { (creds: Creds, ctx: Context) -> String in
r.post("login") { req -> String in
let creds = try req.content.decode(Creds.self)
return "\(creds)"
}

r.on(.POST, to: "large-file", bodyStream: .collect(maxSize: 1_000_000_000)) { (req: HTTPRequest, ctx: Context) -> String in
return req.body.count?.description ?? "none"
r.on(.POST, "large-file", body: .collect(maxSize: 1_000_000_000)) { req -> String in
return req.body.data?.readableBytes.description ?? "none"
}

r.get("json") { (req: HTTPRequest, ctx: Context) -> [String: String] in
r.get("json") { req -> [String: String] in
return ["foo": "bar"]
}.description("returns some test json")

r.webSocket("ws") { (req: HTTPRequest, ctx: Context, ws: WebSocket) -> () in
r.webSocket("ws") { req, ws in
ws.onText { ws, text in
ws.send(text: text.reversed())
}

let ip = ctx.channel.remoteAddress?.description ?? "<no ip>"
let ip = req.channel.remoteAddress?.description ?? "<no ip>"
ws.send(text: "Hello 👋 \(ip)")
}

r.on(.POST, to: "file", bodyStream: .allow) { (req: HTTPRequest, ctx: Context) -> EventLoopFuture<String> in
guard let stream = req.body.stream else {
return ctx.eventLoop.makeFailedFuture(Abort(.badRequest, reason: "Expected streaming body."))
}
let promise = ctx.eventLoop.makePromise(of: String.self)
stream.read { result, stream in
r.on(.POST, "file", body: .stream) { req -> EventLoopFuture<String> in
let promise = req.eventLoop.makePromise(of: String.self)
req.body.drain { result in
switch result {
case .chunk(let chunk):
debugPrint(chunk)
case .buffer(let buffer):
debugPrint(buffer)
case .error(let error):
promise.fail(error)
case .end:
Expand All @@ -48,39 +46,47 @@ public func routes(_ r: Routes, _ c: Container) throws {
}
return promise.futureResult
}

r.get("shutdown") { req -> HTTPStatus in
guard let running = try c.make(Application.self).running else {
throw Abort(.internalServerError)
}
_ = running.stop()
return .ok
}

r.get("hello", ":name") { req in
return req.parameters.get("name") ?? "<nil>"
}

r.get("search") { req in
return req.query["q"] ?? "none"
}

// r.get("hello", String.parameter) { req in
// return try req.parameters.next(String.self)
// }
//
// router.get("search") { req in
// return req.query["q"] ?? "none"
// }
//
let sessions = try r.grouped("sessions").grouped(c.make(SessionsMiddleware.self))
sessions.get("get") { (req: HTTPRequest, ctx: Context) -> String in
return try ctx.session().data["name"] ?? "n/a"
sessions.get("get") { req -> String in
return try req.session().data["name"] ?? "n/a"
}
sessions.get("set", ":value") { (req: HTTPRequest, ctx: Context) -> String in
let name = ctx.parameters.get("value")!
try ctx.session().data["name"] = name
sessions.get("set", ":value") { req -> String in
let name = req.parameters.get("value")!
try req.session().data["name"] = name
return name
}
sessions.get("del") { (req: HTTPRequest, ctx: Context) -> String in
try ctx.destroySession()
sessions.get("del") { req -> String in
try req.destroySession()
return "done"
}

let client = try c.make(Client.self)
r.get("client") { (req: HTTPRequest, ctx: Context) in
return client.get("http://httpbin.org/status/201").map { $0.description }
}
// let client = try c.make(Client.self)
// r.get("client") { req in
// return client.get("http://httpbin.org/status/201").map { $0.description }
// }

let users = r.grouped("users")
users.get { (req: HTTPRequest, ctx: Context) in
users.get { req in
return "users"
}
users.get(.parameter("userID")) { (req: HTTPRequest, ctx: Context) in
users.get(":userID") { req in
return "user"
}
}
13 changes: 2 additions & 11 deletions Sources/BoilerplateRun/main.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,4 @@
import Boilerplate
import Dispatch

do {
let app = try Boilerplate.app(.detect())

// DispatchQueue.global().async {
// sleep(2)
// app.running!.stop()
// }

try app.execute().wait()
}
try app(.detect()).run()

72 changes: 72 additions & 0 deletions Sources/CMultipartParser/include/multipartparser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// MIT License

// Copyright (c) 2019 François Colas

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#ifndef MULTIPARTPARSER_H
#define MULTIPARTPARSER_H
#ifdef __cplusplus
extern "C" {
#endif

#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>

typedef struct multipartparser multipartparser;
typedef struct multipartparser_callbacks multipartparser_callbacks;

typedef int (*multipart_cb) (multipartparser*);
typedef int (*multipart_data_cb) (multipartparser*, const char* data, size_t size);

struct multipartparser {
/** PRIVATE **/
char boundary[70];
int boundary_length;
int index;
uint16_t state;

/** PUBLIC **/
void* data;
};

struct multipartparser_callbacks {
multipart_cb on_body_begin;
multipart_cb on_part_begin;
multipart_data_cb on_header_field;
multipart_data_cb on_header_value;
multipart_cb on_headers_complete;
multipart_data_cb on_data;
multipart_cb on_part_end;
multipart_cb on_body_end;
};

void multipartparser_init(multipartparser* parser, const char* boundary);

void multipartparser_callbacks_init(multipartparser_callbacks* callbacks);

size_t multipartparser_execute(multipartparser* parser,
multipartparser_callbacks* callbacks,
const char* data,
size_t size);

#ifdef __cplusplus
}
#endif
#endif // MULTIPARTPARSER_H
Loading

0 comments on commit af2b50e

Please sign in to comment.