HttpParser.swift 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  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 = self.extractUrlParams(path)
  34. let headers = try nextHeaders(socket)
  35. // TODO detect content-type and handle:
  36. // 'application/x-www-form-urlencoded' -> Dictionary
  37. // 'multipart' -> Dictionary
  38. let body: String?
  39. if let contentLengthString = headers["content-length"],
  40. let contentLength = Int(contentLengthString) {
  41. body = try nextBody(socket, size: contentLength)
  42. // self.extractBodyParams(body!)
  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 extractBodyParams(body: String) -> [String: Parameter] {
  52. let params = body.componentsSeparatedByString("&").map { (param:String) -> (String, String) in
  53. let tokens = param.componentsSeparatedByString("=")
  54. if tokens.count >= 2 {
  55. let key = tokens[0].stringByRemovingPercentEncoding
  56. let value = tokens[1].stringByRemovingPercentEncoding
  57. if key != nil && value != nil { return (key!, value!) }
  58. }
  59. return ("","")
  60. }
  61. var result = [String: Parameter]()
  62. for (key, value) in params {
  63. if let val = result[key] {
  64. switch val {
  65. case .StringValue(let stringValue):
  66. result[key] = Parameter.ArrayString([stringValue, value])
  67. case .ArrayString(var arrayValue):
  68. arrayValue.append(value)
  69. result[key] = Parameter.ArrayString(arrayValue)
  70. }
  71. } else {
  72. result[key] = Parameter.StringValue(value)
  73. }
  74. }
  75. return result
  76. }
  77. private func extractUrlParams(url: String) -> [String : Parameter] {
  78. guard let query = url.componentsSeparatedByString("?").last else {
  79. return [:]
  80. }
  81. return self.extractBodyParams(query)
  82. }
  83. private func nextBody(socket: CInt, size: Int) throws -> String {
  84. var body = ""
  85. var counter = 0;
  86. while counter < size {
  87. let c = nextInt8(socket)
  88. if c < 0 {
  89. throw HttpParserError.ReadBodyFailed
  90. }
  91. body.append(UnicodeScalar(c))
  92. counter++;
  93. }
  94. return body
  95. }
  96. private func nextHeaders(socket: CInt) throws -> [String: String] {
  97. var headers = [String: String]()
  98. while let headerLine = try? nextLine(socket) {
  99. if headerLine.isEmpty {
  100. return headers
  101. }
  102. let headerTokens = headerLine.componentsSeparatedByString(":")
  103. if headerTokens.count >= 2 {
  104. // RFC 2616 - "Hypertext Transfer Protocol -- HTTP/1.1", paragraph 4.2, "Message Headers":
  105. // "Each header field consists of a name followed by a colon (":") and the field value. Field names are case-insensitive."
  106. // We can keep lower case version.
  107. let headerName = headerTokens[0].lowercaseString
  108. let headerValue = headerTokens[1].stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
  109. if !headerName.isEmpty && !headerValue.isEmpty {
  110. headers.updateValue(headerValue, forKey: headerName)
  111. }
  112. }
  113. }
  114. throw HttpParserError.RecvFailed
  115. }
  116. private func nextInt8(socket: CInt) -> Int {
  117. var buffer = [UInt8](count: 1, repeatedValue: 0);
  118. let next = recv(socket as Int32, &buffer, Int(buffer.count), 0)
  119. if next <= 0 { return next }
  120. return Int(buffer[0])
  121. }
  122. private func nextLine(socket: CInt) throws -> String {
  123. var characters: String = ""
  124. var n = 0
  125. repeat {
  126. n = nextInt8(socket)
  127. if ( n > 13 /* CR */ ) { characters.append(Character(UnicodeScalar(n))) }
  128. } while n > 0 && n != 10 /* NL */
  129. if n == -1 && characters.isEmpty {
  130. throw HttpParserError.RecvFailed
  131. }
  132. return characters
  133. }
  134. func supportsKeepAlive(headers: [String: String]) -> Bool {
  135. if let value = headers["connection"] {
  136. return "keep-alive" == value.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()).lowercaseString
  137. }
  138. return false
  139. }
  140. }