HttpParser.swift 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. //
  2. // HttpParser.swift
  3. // Swifter
  4. // Copyright (c) 2014 Damian Kołakowski. All rights reserved.
  5. //
  6. import Foundation
  7. enum HttpParserError : ErrorType {
  8. case RecvFailed
  9. case ReadBodyFailed
  10. case InvalidStatusLine(String)
  11. var value: String {
  12. switch self {
  13. case .RecvFailed:
  14. return "recv(...) failed."
  15. case .ReadBodyFailed:
  16. return "IO error while reading body"
  17. case .InvalidStatusLine(let statusLine):
  18. return "Invalid status line: \(statusLine)"
  19. }
  20. }
  21. }
  22. class HttpParser {
  23. func nextHttpRequest(socket: CInt) throws -> HttpRequest {
  24. do {
  25. let statusLine = try nextLine(socket)
  26. let statusTokens = statusLine.componentsSeparatedByString(" ")
  27. print(statusTokens)
  28. if (statusTokens.count < 3) {
  29. throw HttpParserError.InvalidStatusLine(statusLine)
  30. }
  31. let method = statusTokens[0]
  32. let path = statusTokens[1]
  33. let urlParams = extractUrlParams(path)
  34. // TODO extract query parameters
  35. let headers = try nextHeaders(socket)
  36. // TODO detect content-type and handle:
  37. // 'application/x-www-form-urlencoded' -> Dictionary
  38. // 'multipart' -> Dictionary
  39. let body: String?
  40. if let contentLengthString = headers["content-length"],
  41. let contentLength = Int(contentLengthString) {
  42. body = try nextBody(socket, size: contentLength)
  43. } else {
  44. body = nil
  45. }
  46. return HttpRequest(url: path, urlParams: urlParams, method: method, headers: headers, body: body, capturedUrlGroups: [], address: nil)
  47. } catch {
  48. throw error
  49. }
  50. }
  51. private func extractUrlParams(url: String) -> [(String, String)] {
  52. if let query = url.componentsSeparatedByString("?").last {
  53. return query.componentsSeparatedByString("&").map { (param:String) -> (String, String) in
  54. let tokens = param.componentsSeparatedByString("=")
  55. if tokens.count >= 2 {
  56. let key = tokens[0].stringByRemovingPercentEncoding
  57. let value = tokens[1].stringByRemovingPercentEncoding
  58. if key != nil && value != nil { return (key!, value!) }
  59. }
  60. return ("","")
  61. }
  62. }
  63. return []
  64. }
  65. private func nextBody(socket: CInt, size: Int) throws -> String {
  66. var body = ""
  67. var counter = 0;
  68. while (counter < size) {
  69. let c = nextInt8(socket)
  70. if (c < 0) {
  71. throw HttpParserError.ReadBodyFailed
  72. }
  73. body.append(UnicodeScalar(c))
  74. counter++;
  75. }
  76. return body
  77. }
  78. private func nextHeaders(socket: CInt) throws -> Dictionary<String, String> {
  79. var headers = Dictionary<String, String>()
  80. while let headerLine = try? nextLine(socket) {
  81. if ( headerLine.isEmpty ) {
  82. return headers
  83. }
  84. let headerTokens = headerLine.componentsSeparatedByString(":")
  85. if ( headerTokens.count >= 2 ) {
  86. // RFC 2616 - "Hypertext Transfer Protocol -- HTTP/1.1", paragraph 4.2, "Message Headers":
  87. // "Each header field consists of a name followed by a colon (":") and the field value. Field names are case-insensitive."
  88. // We can keep lower case version.
  89. let headerName = headerTokens[0].lowercaseString
  90. let headerValue = headerTokens[1].stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
  91. if ( !headerName.isEmpty && !headerValue.isEmpty ) {
  92. headers.updateValue(headerValue, forKey: headerName)
  93. }
  94. }
  95. }
  96. throw HttpParserError.RecvFailed
  97. }
  98. private func nextInt8(socket: CInt) -> Int {
  99. var buffer = [UInt8](count: 1, repeatedValue: 0);
  100. let next = recv(socket as Int32, &buffer, Int(buffer.count), 0)
  101. if next <= 0 { return next }
  102. return Int(buffer[0])
  103. }
  104. private func nextLine(socket: CInt) throws -> String {
  105. var characters: String = ""
  106. var n = 0
  107. repeat {
  108. n = nextInt8(socket)
  109. if ( n > 13 /* CR */ ) { characters.append(Character(UnicodeScalar(n))) }
  110. } while ( n > 0 && n != 10 /* NL */)
  111. if ( n == -1 && characters.isEmpty ) {
  112. throw HttpParserError.RecvFailed
  113. }
  114. return characters
  115. }
  116. func supportsKeepAlive(headers: Dictionary<String, String>) -> Bool {
  117. if let value = headers["connection"] {
  118. return "keep-alive" == value.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()).lowercaseString
  119. }
  120. return false
  121. }
  122. }