HaishinKit.swift/Sources/HTTP/HTTPService.swift

319 lines
10 KiB
Swift

import Foundation
enum HTTPVersion: String {
case version10 = "HTTP/1.0"
case version11 = "HTTP/1.1"
}
extension HTTPVersion: CustomStringConvertible {
// MARK: CustomStringConvertible
var description:String {
return rawValue
}
}
// MARK: -
enum HTTPMethod: String {
case get = "GET"
case post = "POST"
case put = "PUT"
case delete = "DELETE"
case head = "HEAD"
case options = "OPTIONS"
case trace = "TRACE"
case connect = "CONNECT"
}
// MARK: -
enum HTTPStatusCode: Int {
case `continue` = 100
case switchingProtocols = 101
case ok = 200
case created = 201
case accepted = 202
case nonAuthoritative = 203
case noContent = 204
case resetContent = 205
case partialContent = 206
case multipleChoices = 300
case movedParmanently = 301
case found = 302
case seeOther = 303
case notModified = 304
case useProxy = 305
case temporaryRedirect = 307
case badRequest = 400
case unauthorixed = 401
case paymentRequired = 402
case forbidden = 403
case notFound = 404
case methodNotAllowed = 405
case notAcceptable = 406
case proxyAuthenticationRequired = 407
case requestTimeOut = 408
case conflict = 409
case gone = 410
case lengthRequired = 411
case preconditionFailed = 412
case requestEntityTooLarge = 413
case requestURITooLarge = 414
case unsupportedMediaType = 415
case requestedRangeNotSatisfiable = 416
case expectationFailed = 417
case internalServerError = 500
case notImplemented = 501
case badGateway = 502
case serviceUnavailable = 503
case gatewayTimeOut = 504
case httpVersionNotSupported = 505
var message:String {
switch self {
case .continue:
return "Continue"
case .switchingProtocols:
return "Switching Protocols"
case .ok:
return "OK"
case .created:
return "Created"
case .accepted:
return "Accepted"
case .nonAuthoritative:
return "Non-Authoritative Information"
case .noContent:
return "No Content"
case .resetContent:
return "Reset Content"
case .partialContent:
return "Partial Content"
case .multipleChoices:
return "Multiple Choices"
case .movedParmanently:
return "Moved Permanently"
case .found:
return "Found"
case .seeOther:
return "See Other"
case .notModified:
return "Not Modified"
case .useProxy:
return "Use Proxy"
case .temporaryRedirect:
return "Temporary Redirect"
case .badRequest:
return "Bad Request"
case .unauthorixed:
return "Unauthorixed"
case .paymentRequired:
return "Payment Required"
case .forbidden:
return "Forbidden"
case .notFound:
return "Not Found"
case .methodNotAllowed:
return "Method Not Allowed"
case .notAcceptable:
return "Not"
case .proxyAuthenticationRequired:
return "Proxy Authentication Required"
case .requestTimeOut:
return "Request Time-out"
case .conflict:
return "Conflict"
case .gone:
return "Gone"
case .lengthRequired:
return "Length Required"
case .preconditionFailed:
return "Precondition Failed"
case .requestEntityTooLarge:
return "Request Entity Too Large"
case .requestURITooLarge:
return "Request-URI Too Large"
case .unsupportedMediaType:
return "Unsupported Media Type"
case .requestedRangeNotSatisfiable:
return "Requested range not satisfiable"
case .expectationFailed:
return "Expectation Failed"
case .internalServerError:
return "Internal Server Error"
case .notImplemented:
return "Not Implemented"
case .badGateway:
return "Bad Gateway"
case .serviceUnavailable:
return "Service Unavailable"
case .gatewayTimeOut:
return "Gateway Time-out"
case .httpVersionNotSupported:
return "HTTP Version not supported"
}
}
}
extension HTTPStatusCode: CustomStringConvertible {
// MARK: CustomStringConvertible
var description:String {
return "\(rawValue) \(message)"
}
}
// MARK: -
open class HTTPService: NetService {
open class var type:String{
return "_http._tcp"
}
open class var defaultPort:Int32 {
return 8080
}
open class var defaultDocument:String {
return "<!DOCTYPE html><html><head><meta charset=\"UTF-8\" /><title>lf</title></head><body>lf</body></html>"
}
var document:String = HTTPService.defaultDocument
func client(inputBuffer client:NetClient) {
guard let request:HTTPRequest = HTTPRequest(data: client.inputBuffer) else {
disconnect(client)
return
}
client.inputBuffer.removeAll()
if (logger.isEnabledFor(level: .trace)) {
logger.trace("\(request):\(self)")
}
switch request.method {
case "GET":
get(request, client: client)
case "POST":
post(request, client: client)
case "PUT":
put(request, client: client)
case "DELETE":
delete(request, client: client)
case "HEAD":
head(request, client: client)
case "OPTIONS":
options(request, client: client)
case "TRACE":
trace(request, client: client)
case "CONNECT":
connect(request, client: client)
default:
notFound(request, client: client)
}
}
open func get(_ request:HTTPRequest, client:NetClient) {
notFound(request, client: client)
}
open func post(_ request:HTTPRequest, client:NetClient) {
notFound(request, client: client)
}
open func put(_ request:HTTPRequest, client:NetClient) {
notFound(request, client: client)
}
open func delete(_ request:HTTPRequest, client:NetClient) {
notFound(request, client: client)
}
open func head(_ request:HTTPRequest, client:NetClient) {
notFound(request, client: client)
}
open func options(_ requst:HTTPRequest, client: NetClient) {
notFound(requst, client: client)
}
open func trace(_ request:HTTPRequest, client:NetClient) {
notFound(request, client: client)
}
open func connect(_ request:HTTPRequest, client:NetClient) {
notFound(request, client: client)
}
func notFound(_ request:HTTPRequest, client:NetClient) {
var response:HTTPResponse = HTTPResponse()
response.statusCode = HTTPStatusCode.notFound.description
client.doOutput(data: response.data)
}
}
open class HLSService: HTTPService {
private(set) var streams:[HTTPStream] = []
open func addHTTPStream(_ stream:HTTPStream) {
for i in 0..<streams.count {
if (stream.name == streams[i].name) {
return
}
}
streams.append(stream)
}
open func removeHTTPStream(_ stream:HTTPStream) {
for i in 0..<streams.count {
if (stream.name == streams[i].name) {
streams.remove(at: i)
return
}
}
}
open override func get(_ request:HTTPRequest, client:NetClient) {
logger.trace("\(request)")
var response:HTTPResponse = HTTPResponse()
// #141
response.headerFields["Access-Control-Allow-Headers"] = "*"
response.headerFields["Access-Control-Allow-Methods"] = "GET,HEAD,OPTIONS"
response.headerFields["Access-Control-Allow-Origin"] = "*"
response.headerFields["Access-Control-Expose-Headers"] = "*"
response.headerFields["Connection"] = "close"
defer {
logger.trace("\(response)")
disconnect(client)
}
switch request.uri {
case "/":
response.headerFields["Content-Type"] = "text/html"
response.body = Data(document.utf8)
client.doOutput(data: response.data)
default:
for stream in streams {
guard let (mime, resource) = stream.getResource(request.uri) else {
break
}
response.statusCode = HTTPStatusCode.ok.description
response.headerFields["Content-Type"] = mime.rawValue
switch mime {
case .VideoMP2T:
if let info:[FileAttributeKey:Any] = try? FileManager.default.attributesOfItem(atPath: resource) {
if let length:Any = info[FileAttributeKey.size] {
response.headerFields["Content-Length"] = String(describing: length)
}
}
client.doOutput(data: response.data)
client.doOutputFromURL(URL(fileURLWithPath: resource), length: 8 * 1024)
default:
response.statusCode = HTTPStatusCode.ok.description
response.body = Data(resource.utf8)
client.doOutput(data: response.data)
}
return
}
response.statusCode = HTTPStatusCode.notFound.description
client.doOutput(data: response.data)
}
}
}