Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2.x.x - HTMLForm example #49

Merged
merged 2 commits into from
Jan 17, 2024
Merged
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

Examples converted to Hummingbird 2.0
- [hello](https://github.com/hummingbird-project/hummingbird-examples/tree/2.x.x/hello) - Basic application setup
- [html-form](https://github.com/hummingbird-project/hummingbird-examples/tree/2.x.x/html-form) - Link HTML form to Hummingbird application
- [http2](https://github.com/hummingbird-project/hummingbird-examples/tree/2.x.x/http2) - Basic application with HTTP2 upgrade added
- [sessions](https://github.com/hummingbird-project/hummingbird-examples/tree/2.x.x/sessions) - Username/password and session authentication.
- [todos-dynamodb](https://github.com/hummingbird-project/hummingbird-examples/tree/2.x.x/todos-dynamodb) - Todos application, based off [TodoBackend](http://todobackend.com) spec, using DynamoDB
Expand All @@ -12,7 +13,6 @@ Examples still working with Hummingbird 1.0
- [auth-jwt](https://github.com/hummingbird-project/hummingbird-examples/tree/main/auth-jwt) - Authentication using JWT.
- [auth-srp](https://github.com/hummingbird-project/hummingbird-examples/tree/main/auth-srp) - Secure Remote Password authentication.
- [graphql-server](https://github.com/hummingbird-project/hummingbird-examples/tree/main/graphql-server) - GraphQL server using [Graphiti](https://github.com/GraphQLSwift/Graphiti)
- [html-form](https://github.com/hummingbird-project/hummingbird-examples/tree/main/html-form) - Link HTML form to Hummingbird application
- [ios-image-server](https://github.com/hummingbird-project/hummingbird-examples/tree/main/ios-image-server) - iOS web server that provides access to iPhone photo library.
- [jobs](https://github.com/hummingbird-project/hummingbird-examples/tree/main/jobs) - Demonstrating offloading of jobs to another server.
- [multipart-form](https://github.com/hummingbird-project/hummingbird-examples/tree/main/multipart-form) - HTML form using Multipart form data, using MultipartKit
Expand Down
19 changes: 5 additions & 14 deletions html-form/Package.swift
Original file line number Diff line number Diff line change
@@ -1,30 +1,21 @@
// swift-tools-version:5.5
// swift-tools-version:5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "html-form",
platforms: [.macOS(.v10_14)],
products: [
.executable(name: "Server", targets: ["Server"]),
],
platforms: [.macOS(.v14)],
dependencies: [
.package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "1.0.0"),
.package(url: "https://github.com/hummingbird-project/hummingbird.git", branch: "2.x.x"),
.package(url: "https://github.com/hummingbird-project/hummingbird-mustache.git", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.3.0"),
],
targets: [
.executableTarget(
name: "Server",
dependencies: [
.byName(name: "App"),
.product(name: "ArgumentParser", package: "swift-argument-parser"),
]
),
.target(
name: "App",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "Hummingbird", package: "hummingbird"),
.product(name: "HummingbirdFoundation", package: "hummingbird"),
.product(name: "HummingbirdMustache", package: "hummingbird-mustache"),
Expand Down
26 changes: 26 additions & 0 deletions html-form/Sources/App/Application+build.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Hummingbird
import HummingbirdMustache
import Logging
import NIOCore

struct HTMLFormRequestContext: HBRequestContext {
var coreContext: HBCoreRequestContext

init(allocator: ByteBufferAllocator, logger: Logger) {
self.coreContext = .init(
requestDecoder: RequestDecoder(),
allocator: allocator,
logger: logger
)
}
}

public func buildApplication(configuration: HBApplicationConfiguration) async throws -> some HBApplicationProtocol {
let library = try HBMustacheLibrary(directory: "templates")
assert(library.getTemplate(named: "head") != nil, "Set your working directory to the root folder of this example to get it to work")

let router = HBRouter(context: HTMLFormRequestContext.self)
WebController(mustacheLibrary: library).addRoutes(to: router)
let app = HBApplication(router: router)
return app
}
19 changes: 0 additions & 19 deletions html-form/Sources/App/Application+configure.swift

This file was deleted.

21 changes: 13 additions & 8 deletions html-form/Sources/App/Controllers/WebController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,28 @@ import HummingbirdMustache
struct HTML: HBResponseGenerator {
let html: String

public func response(from request: HBRequest) throws -> HBResponse {
let buffer = request.allocator.buffer(string: self.html)
return .init(status: .ok, headers: ["content-type": "text/html"], body: .byteBuffer(buffer))
public func response(from request: HBRequest, context: some HBBaseRequestContext) throws -> HBResponse {
let buffer = context.allocator.buffer(string: self.html)
return .init(status: .ok, headers: [.contentType: "text/html"], body: .init(byteBuffer: buffer))
}
}

struct WebController {
let mustacheLibrary: HBMustacheLibrary

func input(request: HBRequest) -> HTML {
let html = mustacheLibrary.render((), withTemplate: "enter-details")!
func addRoutes(to router: HBRouter<some HBRequestContext>) {
router.get("/", use: self.input)
router.post("/", use: self.post)
}

func input(request: HBRequest, context: some HBRequestContext) -> HTML {
let html = self.mustacheLibrary.render((), withTemplate: "enter-details")!
return HTML(html: html)
}

func post(request: HBRequest) throws -> HTML {
guard let user = try? request.decode(as: User.self) else { throw HBHTTPError(.badRequest) }
let html = mustacheLibrary.render(user, withTemplate: "details-entered")!
func post(request: HBRequest, context: some HBRequestContext) async throws -> HTML {
let user = try await request.decode(as: User.self, context: context)
let html = self.mustacheLibrary.render(user, withTemplate: "details-entered")!
return HTML(html: html)
}
}
6 changes: 3 additions & 3 deletions html-form/Sources/App/Extensions/RequestDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ import HummingbirdFoundation
struct RequestDecoder: HBRequestDecoder {
let decoder = URLEncodedFormDecoder()

func decode<T>(_ type: T.Type, from request: HBRequest) throws -> T where T: Decodable {
if request.headers["content-type"].first == "application/x-www-form-urlencoded" {
return try self.decoder.decode(type, from: request)
func decode<T>(_ type: T.Type, from request: HBRequest, context: some HBBaseRequestContext) async throws -> T where T: Decodable {
if request.headers[.contentType] == "application/x-www-form-urlencoded" {
return try await self.decoder.decode(type, from: request, context: context)
}
throw HBHTTPError(.unsupportedMediaType)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
import App
import ArgumentParser
import Hummingbird

struct HummingbirdArguments: ParsableCommand {
@main
struct HummingbirdArguments: AsyncParsableCommand {
@Option(name: .shortAndLong)
var hostname: String = "127.0.0.1"

@Option(name: .shortAndLong)
var port: Int = 8080

func run() throws {
let app = HBApplication(
func run() async throws {
let app = try await buildApplication(
configuration: .init(
address: .hostname(self.hostname, port: self.port),
serverName: "Hummingbird"
)
)
try app.configure()
try app.start()
app.wait()
try await app.runService()
}
}

HummingbirdArguments.main()
28 changes: 13 additions & 15 deletions html-form/Tests/AppTests/AppTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,20 @@ import HummingbirdXCT
import XCTest

final class AppTests: XCTestCase {
func testApp() throws {
let app = HBApplication(testing: .live)
try app.configure()
func testApp() async throws {
let app = try await buildApplication(configuration: .init())

try app.XCTStart()
defer { app.XCTStop() }

let urlencoded = "name=Adam&age=34"
try app.XCTExecute(
uri: "/",
method: .POST,
headers: ["Content-Type": "application/x-www-form-urlencoded"],
body: ByteBufferAllocator().buffer(string: urlencoded)
) { response in
XCTAssertEqual(response.headers["content-type"].first, "text/html")
XCTAssertEqual(response.status, .ok)
try await app.test(.router) { client in
let urlencoded = "name=Adam&age=34"
try await client.XCTExecute(
uri: "/",
method: .post,
headers: [.contentType: "application/x-www-form-urlencoded"],
body: ByteBufferAllocator().buffer(string: urlencoded)
) { response in
XCTAssertEqual(response.headers[.contentType], "text/html")
XCTAssertEqual(response.status, .ok)
}
}
}
}