@@ -483,27 +483,53 @@ class _HTTPServer {
483
483
484
484
struct _HTTPRequest {
485
485
enum Method : String {
486
+ case HEAD
486
487
case GET
487
488
case POST
488
489
case PUT
490
+ case DELETE
489
491
}
490
- let method : Method
491
- let uri : String
492
- let body : String
493
- var messageBody : String ?
494
- var messageData : Data ?
495
- let headers : [ String ]
496
492
497
493
enum Error : Swift . Error {
494
+ case invalidURI
495
+ case invalidMethod
498
496
case headerEndNotFound
499
497
}
500
-
498
+
499
+ let method : Method
500
+ let uri : String
501
+ private( set) var headers : [ String ] = [ ]
502
+ private( set) var parameters : [ String : String ] = [ : ]
503
+ var messageBody : String ?
504
+ var messageData : Data ?
505
+
506
+
501
507
public init ( header: String ) throws {
502
- headers = header. components ( separatedBy: _HTTPUtils. CRLF)
503
- let action = headers [ 0 ]
504
- method = Method ( rawValue: action. components ( separatedBy: " " ) [ 0 ] ) !
505
- uri = action. components ( separatedBy: " " ) [ 1 ]
506
- body = " "
508
+ self . headers = header. components ( separatedBy: _HTTPUtils. CRLF)
509
+ guard headers. count > 0 else {
510
+ throw Error . invalidURI
511
+ }
512
+ let uriParts = headers [ 0 ] . components ( separatedBy: " " )
513
+ guard uriParts. count > 2 , let methodName = Method ( rawValue: uriParts [ 0 ] ) else {
514
+ throw Error . invalidMethod
515
+ }
516
+ method = methodName
517
+ let params = uriParts [ 1 ] . split ( separator: " ? " , maxSplits: 1 , omittingEmptySubsequences: true )
518
+ if params. count > 1 {
519
+ for arg in params [ 1 ] . split ( separator: " & " , omittingEmptySubsequences: true ) {
520
+ let keyValue = arg. split ( separator: " = " , maxSplits: 1 , omittingEmptySubsequences: false )
521
+ guard !keyValue. isEmpty else { continue }
522
+ guard let key = keyValue [ 0 ] . removingPercentEncoding else {
523
+ throw Error . invalidURI
524
+ }
525
+ guard let value = ( keyValue. count > 1 ) ? keyValue [ 1 ] . removingPercentEncoding : " " else {
526
+ throw Error . invalidURI
527
+ }
528
+ self . parameters [ key] = value
529
+ }
530
+ }
531
+
532
+ self . uri = String ( params [ 0 ] )
507
533
}
508
534
509
535
public func getCommaSeparatedHeaders( ) -> String {
@@ -524,32 +550,83 @@ struct _HTTPRequest {
524
550
}
525
551
return nil
526
552
}
553
+
554
+ public func headersAsJSON( ) throws -> Data {
555
+ var headerDict : [ String : String ] = [ : ]
556
+ for header in headers {
557
+ if header. hasPrefix ( method. rawValue) {
558
+ headerDict [ " uri " ] = header
559
+ continue
560
+ }
561
+ let parts = header. components ( separatedBy: " : " )
562
+ if parts. count > 1 {
563
+ headerDict [ parts [ 0 ] ] = parts [ 1 ] . trimmingCharacters ( in: CharacterSet ( charactersIn: " " ) )
564
+ }
565
+ }
566
+ return try JSONSerialization . data ( withJSONObject: headerDict, options: . sortedKeys)
567
+ }
527
568
}
528
569
529
570
struct _HTTPResponse {
530
571
enum Response : Int {
531
572
case OK = 200
532
- case REDIRECT = 302
533
- case NOTFOUND = 404
573
+ case FOUND = 302
574
+ case BAD_REQUEST = 400
575
+ case NOT_FOUND = 404
534
576
case METHOD_NOT_ALLOWED = 405
577
+ case SERVER_ERROR = 500
535
578
}
536
- private let responseCode : Response
537
- private let headers : String
579
+
580
+
581
+ private let responseCode : Int
582
+ private var headers : [ String ]
538
583
public let bodyData : Data
539
584
540
- public init ( response : Response , headers: String = _HTTPUtils . EMPTY , bodyData: Data ) {
541
- self . responseCode = response
585
+ public init ( responseCode : Int , headers: [ String ] = [ ] , bodyData: Data ) {
586
+ self . responseCode = responseCode
542
587
self . headers = headers
543
588
self . bodyData = bodyData
589
+
590
+ for header in headers {
591
+ if header. lowercased ( ) . hasPrefix ( " content-length " ) {
592
+ return
593
+ }
594
+ }
595
+ self . headers. append ( " Content-Length: \( bodyData. count) " )
596
+ }
597
+
598
+ public init ( response: Response , headers: [ String ] = [ ] , bodyData: Data = Data ( ) ) {
599
+ self . init ( responseCode: response. rawValue, headers: headers, bodyData: bodyData)
600
+ }
601
+
602
+ public init ( response: Response , headers: String = _HTTPUtils. EMPTY, bodyData: Data ) {
603
+ let headers = headers. split ( separator: " \r \n " ) . map { String ( $0) }
604
+ self . init ( responseCode: response. rawValue, headers: headers, bodyData: bodyData)
544
605
}
545
606
546
- public init ( response: Response , headers: String = _HTTPUtils. EMPTY, body: String ) {
547
- self . init ( response: response, headers: headers, bodyData: body. data ( using: . utf8) !)
607
+ public init ( response: Response , headers: String = _HTTPUtils. EMPTY, body: String ) throws {
608
+ guard let data = body. data ( using: . utf8) else {
609
+ throw InternalServerError . badBody
610
+ }
611
+ self . init ( response: response, headers: headers, bodyData: data)
612
+ }
613
+
614
+ public init ( responseCode: Int , headers: [ String ] = [ ] , body: String ) throws {
615
+ guard let data = body. data ( using: . utf8) else {
616
+ throw InternalServerError . badBody
617
+ }
618
+ self . init ( responseCode: responseCode, headers: headers, bodyData: data)
548
619
}
549
620
550
621
public var header : String {
551
- let statusLine = _HTTPUtils. VERSION + _HTTPUtils. SPACE + " \( responseCode. rawValue) " + _HTTPUtils. SPACE + " \( responseCode) "
552
- return statusLine + ( headers != _HTTPUtils. EMPTY ? _HTTPUtils. CRLF + headers : _HTTPUtils. EMPTY) + _HTTPUtils. CRLF2
622
+ let responseCodeName = HTTPURLResponse . localizedString ( forStatusCode: responseCode)
623
+ let statusLine = _HTTPUtils. VERSION + _HTTPUtils. SPACE + " \( responseCode) " + _HTTPUtils. SPACE + " \( responseCodeName) "
624
+ let header = headers. joined ( separator: " \r \n " )
625
+ return statusLine + ( header != _HTTPUtils. EMPTY ? _HTTPUtils. CRLF + header : _HTTPUtils. EMPTY) + _HTTPUtils. CRLF2
626
+ }
627
+
628
+ mutating func addHeader( _ header: String ) {
629
+ headers. append ( header)
553
630
}
554
631
}
555
632
@@ -593,57 +670,93 @@ public class TestURLSessionServer {
593
670
responseData. append ( content)
594
671
try httpServer. tcpSocket. writeRawData ( responseData)
595
672
} else {
596
- try httpServer. respond ( with: _HTTPResponse ( response: . NOTFOUND , body: " Not Found " ) )
673
+ try httpServer. respond ( with: _HTTPResponse ( response: . NOT_FOUND , body: " Not Found " ) )
597
674
}
598
675
} else if req. uri. hasPrefix ( " /auth " ) {
599
676
try httpServer. respondWithAuthResponse ( request: req)
600
677
} else if req. uri. hasPrefix ( " /unauthorized " ) {
601
678
try httpServer. respondWithUnauthorizedHeader ( )
602
679
} else {
603
- if req. method == . GET || req. method == . POST || req. method == . PUT {
604
- try httpServer. respond ( with: getResponse ( request: req) )
605
- }
606
- else {
607
- try httpServer. respond ( with: _HTTPResponse ( response: . METHOD_NOT_ALLOWED, body: " Method not allowed " ) )
608
- }
680
+ try httpServer. respond ( with: getResponse ( request: req) )
609
681
}
610
682
}
611
683
612
- func getResponse( request: _HTTPRequest ) -> _HTTPResponse {
684
+ func getResponse( request: _HTTPRequest ) throws -> _HTTPResponse {
685
+
686
+ func headersAsJSONResponse( ) throws -> _HTTPResponse {
687
+ return try _HTTPResponse ( response: . OK, headers: [ " Content-Type: application/json " ] , bodyData: request. headersAsJSON ( ) )
688
+ }
689
+
613
690
let uri = request. uri
691
+ if uri == " /jsonBody " {
692
+ return try headersAsJSONResponse ( )
693
+ }
694
+
695
+ if uri == " /head " {
696
+ guard request. method == . HEAD else { return try _HTTPResponse ( response: . METHOD_NOT_ALLOWED, body: " Method not allowed " ) }
697
+ return try headersAsJSONResponse ( )
698
+ }
699
+
700
+ if uri == " /get " {
701
+ guard request. method == . GET else { return try _HTTPResponse ( response: . METHOD_NOT_ALLOWED, body: " Method not allowed " ) }
702
+ return try headersAsJSONResponse ( )
703
+ }
704
+
705
+ if uri == " /put " {
706
+ guard request. method == . PUT else { return try _HTTPResponse ( response: . METHOD_NOT_ALLOWED, body: " Method not allowed " ) }
707
+ return try headersAsJSONResponse ( )
708
+ }
709
+
710
+ if uri == " /post " {
711
+ guard request. method == . POST else { return try _HTTPResponse ( response: . METHOD_NOT_ALLOWED, body: " Method not allowed " ) }
712
+ return try headersAsJSONResponse ( )
713
+ }
714
+
715
+ if uri == " /delete " {
716
+ guard request. method == . DELETE else { return try _HTTPResponse ( response: . METHOD_NOT_ALLOWED, body: " Method not allowed " ) }
717
+ return try headersAsJSONResponse ( )
718
+ }
719
+
614
720
if uri == " /upload " {
615
- let text = " Upload completed! "
616
- return _HTTPResponse ( response: . OK, headers: " Content-Length: \( text. data ( using: . utf8) !. count) " , body: text)
721
+ if let contentLength = request. getHeader ( for: " content-length " ) {
722
+ let text = " Upload completed!, Content-Length: \( contentLength) "
723
+ return try _HTTPResponse ( response: . OK, body: text)
724
+ }
725
+ if let te = request. getHeader ( for: " transfer-encoding " ) , te == " chunked " {
726
+ return try _HTTPResponse ( response: . OK, body: " Received Chunked request " )
727
+ } else {
728
+ return try _HTTPResponse ( response: . BAD_REQUEST, body: " Missing Content-Length " )
729
+ }
617
730
}
618
731
619
732
if uri == " /country.txt " {
620
733
let text = capitals [ String ( uri. dropFirst ( ) ) ] !
621
- return _HTTPResponse ( response: . OK, headers : " Content-Length: \( text . data ( using : . utf8 ) ! . count ) " , body: text)
734
+ return try _HTTPResponse ( response: . OK, body: text)
622
735
}
623
736
624
737
if uri == " /requestHeaders " {
625
738
let text = request. getCommaSeparatedHeaders ( )
626
- return _HTTPResponse ( response: . OK, headers : " Content-Length: \( text . data ( using : . utf8 ) ! . count ) " , body: text)
739
+ return try _HTTPResponse ( response: . OK, body: text)
627
740
}
628
741
629
742
if uri == " /emptyPost " {
630
- if request. body . count == 0 && request . getHeader ( for: " Content-Type " ) == nil {
631
- return _HTTPResponse ( response: . OK, body: " " )
743
+ if request. getHeader ( for: " Content-Type " ) == nil {
744
+ return try _HTTPResponse ( response: . OK, body: " " )
632
745
}
633
- return _HTTPResponse ( response: . NOTFOUND , body: " " )
746
+ return try _HTTPResponse ( response: . NOT_FOUND , body: " " )
634
747
}
635
748
636
749
if uri == " /requestCookies " {
637
- return _HTTPResponse ( response: . OK, headers: " Set-Cookie: fr=anjd&232; Max-Age=7776000; path=/ \r \n Set-Cookie: nm=sddf&232; Max-Age=7776000; path=/; domain=.swift.org; secure; httponly \r \n " , body: " " )
750
+ return try _HTTPResponse ( response: . OK, headers: " Set-Cookie: fr=anjd&232; Max-Age=7776000; path=/ \r \n Set-Cookie: nm=sddf&232; Max-Age=7776000; path=/; domain=.swift.org; secure; httponly \r \n " , body: " " )
638
751
}
639
752
640
753
if uri == " /echoHeaders " {
641
754
let text = request. getCommaSeparatedHeaders ( )
642
- return _HTTPResponse ( response: . OK, headers: " Content-Length: \( text. data ( using: . utf8) !. count) " , body: text)
755
+ return try _HTTPResponse ( response: . OK, headers: " Content-Length: \( text. data ( using: . utf8) !. count) " , body: text)
643
756
}
644
757
645
758
if uri == " /redirectToEchoHeaders " {
646
- return _HTTPResponse ( response: . REDIRECT , headers: " location: /echoHeaders \r \n Set-Cookie: redirect=true; Max-Age=7776000; path=/ " , body: " " )
759
+ return try _HTTPResponse ( response: . FOUND , headers: " location: /echoHeaders \r \n Set-Cookie: redirect=true; Max-Age=7776000; path=/ " , body: " " )
647
760
}
648
761
649
762
if uri == " /UnitedStates " {
@@ -654,7 +767,7 @@ public class TestURLSessionServer {
654
767
let port = host. components ( separatedBy: " : " ) [ 1 ]
655
768
let newPort = Int ( port) ! + 1
656
769
let newHost = ip + " : " + String( newPort)
657
- let httpResponse = _HTTPResponse ( response: . REDIRECT , headers: " Location: http:// \( newHost + " / " + value) " , body: text)
770
+ let httpResponse = try _HTTPResponse ( response: . FOUND , headers: " Location: http:// \( newHost + " / " + value) " , body: text)
658
771
return httpResponse
659
772
}
660
773
@@ -680,26 +793,26 @@ public class TestURLSessionServer {
680
793
<!ELEMENT real (#PCDATA)> <!-- Contents should represent a floating point number matching ( " + " | " - " )? d+ ( " . " d*)? ( " E " ( " + " | " - " ) d+)? where d is a digit 0-9. -->
681
794
<!ELEMENT integer (#PCDATA)> <!-- Contents should represent a (possibly signed) integer number in base 10 -->
682
795
"""
683
- return _HTTPResponse ( response: . OK, body: dtd)
796
+ return try _HTTPResponse ( response: . OK, body: dtd)
684
797
}
685
798
686
799
if uri == " /UnitedKingdom " {
687
800
let value = capitals [ String ( uri. dropFirst ( ) ) ] !
688
801
let text = request. getCommaSeparatedHeaders ( )
689
802
//Response header with only path to the location to redirect.
690
- let httpResponse = _HTTPResponse ( response: . REDIRECT , headers: " Location: \( value) " , body: text)
803
+ let httpResponse = try _HTTPResponse ( response: . FOUND , headers: " Location: \( value) " , body: text)
691
804
return httpResponse
692
805
}
693
806
694
807
if uri == " /echo " {
695
- return _HTTPResponse ( response: . OK, body: request. messageBody ?? request . body )
808
+ return try _HTTPResponse ( response: . OK, body: request. messageBody ?? " " )
696
809
}
697
810
698
811
if uri == " /redirect-with-default-port " {
699
812
let text = request. getCommaSeparatedHeaders ( )
700
813
let host = request. headers [ 1 ] . components ( separatedBy: " " ) [ 1 ]
701
814
let ip = host. components ( separatedBy: " : " ) [ 0 ]
702
- let httpResponse = _HTTPResponse ( response: . REDIRECT , headers: " Location: http:// \( ip) /redirected-with-default-port " , body: text)
815
+ let httpResponse = try _HTTPResponse ( response: . FOUND , headers: " Location: http:// \( ip) /redirected-with-default-port " , body: text)
703
816
return httpResponse
704
817
705
818
}
@@ -716,11 +829,42 @@ public class TestURLSessionServer {
716
829
bodyData: helloWorld)
717
830
}
718
831
832
+ // Look for /xxx where xxx is a 3digit HTTP code
833
+ if uri. hasPrefix ( " / " ) && uri. count == 4 , let code = Int ( String ( uri. dropFirst ( ) ) ) , code > 0 && code < 1000 {
834
+ return try statusCodeResponse ( forRequest: request, statusCode: code)
835
+ }
836
+
719
837
guard let capital = capitals [ String ( uri. dropFirst ( ) ) ] else {
720
- return _HTTPResponse ( response: . NOTFOUND, body: " Not Found " )
838
+ return _HTTPResponse ( response: . NOT_FOUND)
839
+ }
840
+ return try _HTTPResponse ( response: . OK, body: capital)
841
+ }
842
+
843
+ private func statusCodeResponse( forRequest request: _HTTPRequest , statusCode: Int ) throws -> _HTTPResponse {
844
+ guard let bodyData = try ? request. headersAsJSON ( ) else {
845
+ return try _HTTPResponse ( response: . SERVER_ERROR, body: " Cant convert headers to JSON object " )
846
+ }
847
+
848
+ var response : _HTTPResponse
849
+ switch statusCode {
850
+ case 300 ... 303 , 305 ... 308 :
851
+ let location = request. parameters [ " location " ] ?? " / " + request. method. rawValue. lowercased ( )
852
+ let body = " Redirecting to \( request. method) \( location) "
853
+ let headers = [ " Content-Type: test/plain " , " Location: \( location) " ]
854
+ response = try _HTTPResponse ( responseCode: statusCode, headers: headers, body: body)
855
+
856
+ case 401 :
857
+ let headers = [ " Content-Type: application/json " , " Content-Length: \( bodyData. count) " ]
858
+ response = _HTTPResponse ( responseCode: statusCode, headers: headers, bodyData: bodyData)
859
+ response. addHeader ( " WWW-Authenticate: Basic realm= \" Fake Relam \" " )
860
+
861
+ default :
862
+ let headers = [ " Content-Type: application/json " , " Content-Length: \( bodyData. count) " ]
863
+ response = _HTTPResponse ( responseCode: statusCode, headers: headers, bodyData: bodyData)
864
+ break
721
865
}
722
- return _HTTPResponse ( response: . OK, body: capital)
723
866
867
+ return response
724
868
}
725
869
}
726
870
@@ -744,6 +888,7 @@ extension ServerError : CustomStringConvertible {
744
888
enum InternalServerError : Error {
745
889
case socketAlreadyClosed
746
890
case requestTooShort
891
+ case badBody
747
892
}
748
893
749
894
public class ServerSemaphore {
0 commit comments