45 Commits

Author SHA1 Message Date
Ian Partridge 07cd40ce8c Update README.md 2018-09-24 11:14:17 +01:00
Carl Brown 8a5668cc19 Merge pull request #109 from gtaban/tlsRebased
TLS Integration into HTTP
2018-01-10 15:14:12 -06:00
Gelareh Taban 21cf431cf1 Remove extra logging. 2018-01-10 15:06:45 -06:00
Gelareh Taban 6b8cd8b092 Remove code that crept back because of the rebase. 2018-01-10 14:26:45 -06:00
Gelareh Taban 7bf242651c Update dependency on swift-server/TLSService instead of gtaban/TLSService. 2018-01-10 11:54:51 -06:00
Carl Brown 30344ad3f0 Move TLS config to the HTTPServer.Options object 2018-01-08 15:29:21 -06:00
Gelareh Taban 9b64bc21d8 TLS integration with tests - runs OK on macOS and OK on Linux 2018-01-08 12:47:44 -06:00
gtaban b5001e49c6 Merge pull request #108 from carlbrown/forPRforTSAN
Add Semaphores to fix TSAN warnings
2018-01-08 11:33:21 -06:00
Carl Brown 3739ce6037 Remove TODO comment for thing I'd already done. 2018-01-08 11:07:29 -06:00
Carl Brown 36adb9f32f Remove Logging variable no longer used and fixed typo
Thanks @gtaban for the feedback.
2018-01-08 10:57:54 -06:00
Carl Brown 66c6b6a06d Semaphores to fix TSAN warnings
Semaphores to track if abort() has been called

Semaphores to track if cleanup() has been called and only let it get called once
2018-01-03 14:06:30 -06:00
Carl Brown b576f2e860 Merge pull request #107 from ZeeZide/bug/http-parser-credits
Add http-parser credits
2017-12-08 14:39:31 -06:00
Helge Hess 3af439047d Apply original http-parser 2.7.1 drop
The internal one only changes formatting, but for maintaining
the drop, this should be an exact copy, IMO :-)
2017-12-07 21:37:45 +01:00
Helge Hess 933ecc536b Use 2.7.1 version of LICENSE
They changed it in master, see 89279ab for why. For the
2.7.1 drop, we should stick to the associated one.
2017-12-07 21:34:45 +01:00
Helge Hess dd00ed6e3b Add HTTPParser license and credits
... omitting them may be legal or not, but it isn't nice
in any case ;-)
2017-12-07 21:25:09 +01:00
Carl Brown 31d066a0e9 Merge pull request #103 from anayini/update-readme
Add note in README.md about generating .xcodeproj
2017-11-30 11:42:58 -06:00
Arjun Nayini e002bc58eb PR Feedback 2017-11-22 22:01:34 -08:00
Arjun Nayini d50eeecf76 Add note in README.md about generating .xcodeproj 2017-11-13 22:03:51 -08:00
Carl Brown 4eab465956 Merge pull request #101 from GeorgeLyon/develop
Any informational header (1XX) should be allowed to be written prior …
2017-11-13 09:49:57 -06:00
George 92dcc60699 Any informational header (1XX) should be allowed to be written prior to actual response headers.
Previously, this honor was only bestowed upon status 100 "Continue".
2017-11-12 12:45:59 -08:00
Carl Brown 825b680c64 Merge pull request #81 from gtaban/convertOptions
Move HTTPServer options from Start into init
2017-11-10 17:29:08 -06:00
Gelareh Taban 2e987e181c remove arguments from HTTPServer start function and put it in an option class and pass in init. 2017-11-10 16:50:33 -06:00
Carl Brown 397c5fb7a4 Merge pull request #85 from ZeeZide/feature/no-http-serving
Drop HTTPServing
2017-11-10 15:53:31 -06:00
Carl Brown b6211a2894 Merge branch 'develop' into feature/no-http-serving 2017-11-10 15:25:22 -06:00
Carl Brown 4d2c2b55f7 Merge branch 'develop' into feature/no-http-serving 2017-11-10 15:23:14 -06:00
Carl Brown b54c7b40ad Merge pull request #95 from gtaban/splitServerTests
Divide up the Server unit tests and end-to-end tests
2017-11-10 14:32:36 -06:00
Carl Brown 9dd0946bff Merge pull request #94 from gtaban/PackageResolved
git should ignore Package.resolved
2017-11-10 14:28:58 -06:00
Gelareh Taban a5a4da0375 Divide up the Server unit tests and end to end tests. 2017-11-10 10:52:02 -06:00
Gelareh Taban 6ad4650c17 git should ignore Package.resolved 2017-11-10 10:34:19 -06:00
Arjun Nayini 2c4336880d [Code Style] Some code style changes (#92)
* [Code Style] Some code style changes

Minor changes to make some code Swiftier

* Fixes for PR feedback
2017-11-09 19:58:39 +00:00
Chris Amanse b624e39530 Fix readBuffer leak when do block catches an error (#77) 2017-11-09 19:57:34 +00:00
Carl Brown 53e1e38e1b server.stop shouldn't have been left commented out in these two tests (#75) 2017-11-07 12:24:41 -08:00
Carl Brown 630bd0bfc9 Handle blocking failure (#62) 2017-11-07 12:18:19 -08:00
Arjun Nayini 728b66d569 Lint and Clean up code (#84) 2017-11-07 11:34:33 -08:00
Helge Hess 88ab09d047 Drop HTTPServing
The API is concrete, this protocol serves no purpose.
2017-11-05 23:51:45 +01:00
Shane Vitarana 473177815d Fixed typo in comments (#80) 2017-10-30 10:10:21 +00:00
Carl Brown fa11340bc1 Merge pull request #79 from ZeeZide/develop
Typo: it is mkcol, not mkol
2017-10-26 09:45:03 -05:00
Helge Hess 34cd525151 Typo: it is mkcol, not mkol
As another option we could write an RFC to introduce

  MKOL - Make Outline

Not quite sure what that would do, though.
2017-10-23 14:57:41 +02:00
Carl Brown d9dbb40531 Sockets aren't idle if response not completed (#63) 2017-10-07 18:18:11 +01:00
Chris Amanse 1994157441 Test hashValue and description of HTTPVersion (#71) 2017-10-07 17:54:49 +01:00
Carl Brown ec6087c2a1 dealloc (#68) 2017-10-07 13:19:06 +01:00
nixzhu fb6df9b933 Better code style (#67)
* Indent return

* Auto infer URLSessionConfiguration.default

* Better define of `testString`
2017-10-07 13:17:09 +01:00
Carl Brown 5a00cde492 Merge pull request #64 from ShaneQi/new_runloop_api_in_readme
Replaced `CFRunLoopRun()` with `RunLoop.current.run()` in README examples.
2017-10-04 10:18:22 -05:00
Shane Qi fffb8dd883 Replaced CFRunLoopRun() with RunLoop.current.run() in README examples.
Linux's Foundation donesn't have `CFRunLoopRun()` API. It's equivalent is `RunLoop.current.run()`.
Since `RunLoop.current.run()` is also available on macOS, so we can replace `CFRunLoopRun()` with `RunLoop.current.run()`, so that people won't be confused when they copy & past example but not working on linux.
2017-10-04 09:19:00 -05:00
Ian Partridge 234551eeef Readme: request feedback (#61) 2017-10-03 13:32:33 +01:00
37 changed files with 4229 additions and 2925 deletions
+1
View File
@@ -73,4 +73,5 @@ fastlane/test_output
*.orig
/.idea
/Package.pins
/Package.resolved
docs
+1 -1
View File
@@ -385,7 +385,7 @@ public struct HTTPMethod : Hashable, CustomStringConvertible, ExpressibleByInteg
public static let trace
public static let copy
public static let lock
public static let mkol
public static let mkcol
public static let move
public static let propfind
public static let proppatch
@@ -0,0 +1,74 @@
# Create self-signed certificate with openssl
```
// generate an RSA key
openssl genrsa -out key.pem 2048
// create cert signing request used to generate the cert
openssl req -new -sha256 -key key.pem -out cert.csr
// create cert
openssl req -x509 -sha256 -days 365 -key key.pem -in cert.csr -out cert.pem
// convert cert into PKCS#12 format:
openssl pkcs12 -export -out cert.pfx -inkey key.pem -in cert.pem
```
sw!ft!sC00l
Alternatively, use: https://www.sslshopper.com/ssl-converter.html
# Create certificate chain CA->intermediate->server
// Based on https://jamielinux.com/docs/openssl-certificate-authority/introduction.html
// Use above link to
```
// create root key
openssl genrsa -out ca.key.pem 4096
//create root certificate
openssl req -config openssl.cnf -key ca.key.pem -new -x509 -days 7300 -sha256 extensions v3_ca -out ca.cert.pem
//verify root cert
openssl x509 -noout -text -in ca.cert.pem
// create intermediate key
openssl genrsa -out intermediate/intermediate.key.pem 4096
// create a certificate signing request
openssl req -config intermediate/openssl.cnf -new -sha256 -key intermediate/intermediate.key.pem -out intermediate/intermediate.csr.pem
// create certificate
openssl ca -config openssl.cnf -extensions v3_intermediate_ca -days 3650 -notext -md sha256 -in intermediate/intermediate.csr.pem -out intermediate/intermediate.cert.pem
// verify intermediate certificate
openssl x509 -noout -text -in intermediate/intermediate.cert.pem
// verify intermediate certificate against the root certificate
openssl verify -CAfile ca.cert.pem intermediate/intermediate.cert.pem
// create the certificate chain
cat intermediate/intermediate.cert.pem ca.cert.pem > intermediate/ca-chain.cert.pem
// create server key
openssl genrsa -out intermediate/server.key.pem 2048
openssl req -config intermediate/openssl.cnf -key intermediate/server.key.pem -new -sha256 -out intermediate/server.csr.pem
openssl ca -config intermediate/openssl.cnf -extensions server_cert -days 375 -notext -md sha256 -in intermediate/server.csr.pem -out intermediate/server.cert.pem
// verify cert
openssl x509 -noout -text -in server.cert.pem
// verify chain of trust
openssl verify -CAfile ca-chain.cert.pem server.cert.pem
// Convert to PKCS12 for testing on mac
openssl pkcs12 -export -out certificate.pfx -inkey privateKey.key -in certificate.crt -certfile CACert.crt
or better yet! use: https://www.sslshopper.com/ssl-converter.html
```
+17
View File
@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICtTCCAZ0CAQAwcDELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUtU3RhdGUx
EjAQBgNVBAoTCVN3aWZ0Lm9yZzEeMBwGA1UECxMVU3dpZnQub3JnIFNlcnZlciBB
UElzMRgwFgYDVQQDEw9UTFNTZXJ2aWNlIEFQSXMwggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQDD+dYrPDCSrXMqK1eXReDpuf5KzMnrzhzNInCGUDQkfchv
B/sp/OzP//bnYhIGlBdlbiQyxnW30Q7I/xlfFbIs4P7Rje/szUC03/OiJM4RN36W
ry7iO38vH9LO8JlTjGhJ+AMEgdmcfhMDeuaWYUcnh3sPyQ9CLRCzFbAtvIKC9U/h
HSaBEricbvunJ91pdxTMloPwEN+5qH2lDbyuYI2mJgdxtkXuDpoOMt0qdqORyiqv
t0YUQvQyLzQCGUwiP71aBO9auVrpqE9rHcqN4N3pP0eSjMWzbUptfTNrydz06Eom
RFSeha0QKgvK4ka4J0ONvvnTqM8SJDM5Nv3WQ1s9AgMBAAGgADANBgkqhkiG9w0B
AQsFAAOCAQEANDOSLVOj7r9Ct0vHQTaP/Jm29Fm0D/C0HT5RfRpDF2NP5SPKUYU5
Gnzy4CXfIVDOLqZ5T3fCYnyAfeEBrtjO3Ygnw7Qbfu/rRJgqkX87Vq71IvEvQHls
H5JHMqBJT5m9S8fdqS04XFYZoqvrdPNp8vtFWvUjQ5m0bdw+qAE1p+RV+rWIjHnT
FZ7D+DZKjKRaanbP2iNX8wlqdg8+/HhY2MDdSscFP8EZwu7mAEIt2J0ZNuMPIN4Y
4BzzYEgh4rzWd7FwYPhl9+t3EwUSJPqo5B9Ar7CUZ1bvbVjjU63mG68Aa68S7KdZ
C3qVAaiYpRku5b1K/VAuM17/ahaFZGfhHw==
-----END CERTIFICATE REQUEST-----
+25
View File
@@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIEOzCCAyOgAwIBAgIJAPkaoUuAHqnVMA0GCSqGSIb3DQEBCwUAMHAxCzAJBgNV
BAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRIwEAYDVQQKEwlTd2lmdC5vcmcx
HjAcBgNVBAsTFVN3aWZ0Lm9yZyBTZXJ2ZXIgQVBJczEYMBYGA1UEAxMPVExTU2Vy
dmljZSBBUElzMCAXDTE3MDYwNTE0MzcwNFoYDzIxMTcwNTEyMTQzNzA0WjBwMQsw
CQYDVQQGEwJVUzETMBEGA1UECBMKU29tZS1TdGF0ZTESMBAGA1UEChMJU3dpZnQu
b3JnMR4wHAYDVQQLExVTd2lmdC5vcmcgU2VydmVyIEFQSXMxGDAWBgNVBAMTD1RM
U1NlcnZpY2UgQVBJczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMP5
1is8MJKtcyorV5dF4Om5/krMyevOHM0icIZQNCR9yG8H+yn87M//9udiEgaUF2Vu
JDLGdbfRDsj/GV8Vsizg/tGN7+zNQLTf86IkzhE3fpavLuI7fy8f0s7wmVOMaEn4
AwSB2Zx+EwN65pZhRyeHew/JD0ItELMVsC28goL1T+EdJoESuJxu+6cn3Wl3FMyW
g/AQ37mofaUNvK5gjaYmB3G2Re4Omg4y3Sp2o5HKKq+3RhRC9DIvNAIZTCI/vVoE
71q5WumoT2sdyo3g3ek/R5KMxbNtSm19M2vJ3PToSiZEVJ6FrRAqC8riRrgnQ42+
+dOozxIkMzk2/dZDWz0CAwEAAaOB1TCB0jAdBgNVHQ4EFgQUjuA8rn7EEEK2aSl2
LzcHT3bIpKIwgaIGA1UdIwSBmjCBl4AUjuA8rn7EEEK2aSl2LzcHT3bIpKKhdKRy
MHAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRIwEAYDVQQKEwlT
d2lmdC5vcmcxHjAcBgNVBAsTFVN3aWZ0Lm9yZyBTZXJ2ZXIgQVBJczEYMBYGA1UE
AxMPVExTU2VydmljZSBBUElzggkA+RqhS4AeqdUwDAYDVR0TBAUwAwEB/zANBgkq
hkiG9w0BAQsFAAOCAQEAtWnk3Z3Ae6FuhGLwLAFiWQ3p+JK2mGP7BjnnyoJzuXiy
NIgMru6vJb/3O+xO3TTypRLcGHgHZgRc16VBIw4rgO2/Kr8Ij5N84L3TL+7o3hLc
0JRb+3jOADAC5LbDVKkW8li1CKFbg3H6B++9ccs72GsGSSBOCnYegUU7sc32roWo
0f1P8JjDntX4bPzzAIprVjENXx4WZf2VNGJbTsRf3lsU9/ROJwuJhuBWDDe0H657
guWIMDbxVlJoNciNH4Xxr8GNRpb97SHrgT+bNnO8uq4MT7q5xDAx452v4wJYG/B4
Iej9MelHCb0L9etQ0N9/GGJyyUD1ZDRTkup5iMsT/A==
-----END CERTIFICATE-----
Binary file not shown.
+27
View File
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAw/nWKzwwkq1zKitXl0Xg6bn+SszJ684czSJwhlA0JH3Ibwf7
Kfzsz//252ISBpQXZW4kMsZ1t9EOyP8ZXxWyLOD+0Y3v7M1AtN/zoiTOETd+lq8u
4jt/Lx/SzvCZU4xoSfgDBIHZnH4TA3rmlmFHJ4d7D8kPQi0QsxWwLbyCgvVP4R0m
gRK4nG77pyfdaXcUzJaD8BDfuah9pQ28rmCNpiYHcbZF7g6aDjLdKnajkcoqr7dG
FEL0Mi80AhlMIj+9WgTvWrla6ahPax3KjeDd6T9HkozFs21KbX0za8nc9OhKJkRU
noWtECoLyuJGuCdDjb7506jPEiQzOTb91kNbPQIDAQABAoIBAETt/Blq6z68CTS8
1+saJfivdbTDgQYSkejJA0EKtrxbDYOYEAx9rKGgSyypPuPXdL81VUASs9b6jjO+
HiNmkyvb22TDgq8MpoS3/I58WYqOtVS0u03RVXOywsgMsjFDwm657/3G2k6DvZZM
xQwBnTBXI76ynk5NYYFL0JLVqiX7GxLHRP0vWIemi+nsLDxNuH2Z03dZcaT/crAi
C8OyxKqYjnPRQsWmfrIaqstFlB+saj41P5k1MprHFOeoYv5zRQ+vJI34X+tw/xni
vUBK6zLmF7ElffFBNSuasIVKKGVTn/NECIlPOTZcfEPaJxN9YgcK+v1Uo17IIfvr
V6HIf0ECgYEA4YCCKm0lzl4VZF8JtJhW7FnsWU5RCPPFedUmVJa+7gO1DBgqObeT
E8OL++YDoEff2m/Sb23isWqe0FYztFYZ2g/lWglznKNRjUunTIKoQ/6RdQvNPDL6
oVgr24j55JAg2Ios5HljhNyraWoJpd2jGknk/imu26Y69RkXA7VmJlECgYEA3nsO
Pkv2LjBd7VMF0yniO/q9nK/BOi0aZy4x+N7XZ6sXHPOqYdnHhI1tVK37z0+NEe2P
owlA2Dg2tqVXV694SYqOmCMYY2uRBkIjEGJ9GK4xirPTGsS/OB34ga1oeo85qfkR
z0MzE5UQA3NPfp5Lo1MCxjsACkiiKwi10lW57y0CgYBYj2mPU/JHC7gHBTQAktuA
Uwh5QkKc62+gm09EZTdyrk4KA+uBY1EFsARn1zuRVOjbFpNkY5ll5+ObGl+P8UiR
1TBTneajm5hJj26So7WFjpJ9jzb472RyvPfsbe0GEHx1zj43NF0bLra63YQQeey2
RFMEZkZfyPbajxH0yObnIQKBgQDAINzIB6ltce9rR8s79GufCIY+jbj8mH64pDgb
h7XVnPa01ehJ4FxgqBHGkwlmmnhlBxaH4THSh5kYWej3nFzwhWtnDse88+Ol1++X
8rW9XpECCxE/iLDpsVguBKa5UH6nvqQWrR2qx4uwrx/zZJhFTyaSMdlzCA6jwz3h
io6rcQKBgBknilfMBkHRfaMhcVHWc2aiUsMBSogjY0Wf84mvLW0rDLfwPe5JX3qq
VwyXqcFfJ5oZMFmT7+ohcQybhDSdaE4TMkluFSFrYO2HRHzHg0i+K4NjtYhQSxmL
25X1Ravg9zXcR6O22jsXVPpRrOm2qA0q5pC9WcVqwYZZjhmzu6A4
-----END RSA PRIVATE KEY-----
+1
View File
@@ -0,0 +1 @@
sw!ft!sC00l
+36
View File
@@ -0,0 +1,36 @@
-----BEGIN CERTIFICATE-----
MIIGJTCCBQ2gAwIBAgISA8I9tomI5N82h2iMz35TyPcSMA0GCSqGSIb3DQEBCwUA
MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xNzEwMTYxNTAzNThaFw0x
ODAxMTQxNTAzNThaMBoxGDAWBgNVBAMTD3NzbC5nZWxhcmVoLnh5ejCCAiIwDQYJ
KoZIhvcNAQEBBQADggIPADCCAgoCggIBAKlEE4VdwjkBI/mACJwnAfAGYGk1dimU
js+x58JnRvVZfMQQ5f7EGC4ezX2ra9gCngBv+U48P378uMCSxaGAmnyNO2vxWciz
v5K5+id2+RMlHkXHlrCKj1TarqUe2W02GdsLiBBXKTBjiC+1dXRztZVXudcbagbz
fbjKQIoEPbR+VYBr8WJsL/FpZcXC2Gx2uBO07mtK+WQGo2UpZOE65m0yg74//Eyk
L+hyt6PQEvznazSXjpVK+lBS0YwPOK2CQRT7kLUaVCIFf+O6Lg8I1EgEGldc91xh
op9H1nBUMIw3xc0FMo5d15f79+yOh91mDWY1F82RidLo8ZaPDgDsy/iL7NrT7plQ
qACVSKWqRqvXYIDbF19z9urVqyvnmFlqXtsuf4Iv5JCnnqHQ4852rm9w5sc6mVny
ikhmxPABHu3cpBtWDYhyow4FQXi0HAefi8vNw4E1O5lzCcJUgPvj/svsCRn/YgSH
Zq2bEGZCsczAiLHGGaIqpsNcnoZOTYN1OU0nqLHY0WGgyx2KzsDWS03+p6Ecm5QF
h2DOzO/moF/dsr707wEpgsKrQZ9scvck9BDkc3wCzoH+qAjcFlWdnQk+8/5aldgx
BYeOUgE3V4gGCYYggENVLwinH8/c+umpYkhq//SGTff/o5l0u1hp/Gf/WJ5TaZKk
nRrATni5hkRtAgMBAAGjggIzMIICLzAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYw
FAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFFcp
eRLK7uObBVrJyJk3t3tV1qA7MB8GA1UdIwQYMBaAFKhKamMEfd265tE5t6ZFZe/z
qOyhMG8GCCsGAQUFBwEBBGMwYTAuBggrBgEFBQcwAYYiaHR0cDovL29jc3AuaW50
LXgzLmxldHNlbmNyeXB0Lm9yZzAvBggrBgEFBQcwAoYjaHR0cDovL2NlcnQuaW50
LXgzLmxldHNlbmNyeXB0Lm9yZy8wPgYDVR0RBDcwNYIPc3NsLmdlbGFyZWgueHl6
ghBzc2wxLmdlbGFyZWgueHl6ghBzc2wyLmdlbGFyZWgueHl6MIH+BgNVHSAEgfYw
gfMwCAYGZ4EMAQIBMIHmBgsrBgEEAYLfEwEBATCB1jAmBggrBgEFBQcCARYaaHR0
cDovL2Nwcy5sZXRzZW5jcnlwdC5vcmcwgasGCCsGAQUFBwICMIGeDIGbVGhpcyBD
ZXJ0aWZpY2F0ZSBtYXkgb25seSBiZSByZWxpZWQgdXBvbiBieSBSZWx5aW5nIFBh
cnRpZXMgYW5kIG9ubHkgaW4gYWNjb3JkYW5jZSB3aXRoIHRoZSBDZXJ0aWZpY2F0
ZSBQb2xpY3kgZm91bmQgYXQgaHR0cHM6Ly9sZXRzZW5jcnlwdC5vcmcvcmVwb3Np
dG9yeS8wDQYJKoZIhvcNAQELBQADggEBAB2pZwz/o9x2+/30zrubOTeuVXLVbjDq
uqbvmxbUNbCcn2putCiLQ804O1A0bLSLHgj0gKx9eE6bByTBa9k8LeLZB5UCQ9Ns
OOZSbzj7d4n+KRg0YrUApH/I3H2C43LK0oRvjdIX/KBEEdsXeML6FwuJnWb84ZEN
ZfPVBhrw2aH17EIfm7TcXo8CLjxhuJDA2OOwZVUg7ZMkIvP3EIhsBuyBuIMGHT/7
qaeVT7qeSQ3KhOguBnSkmxBwfn/aVZ83p9AvsZX5xuzhg5RCKx3u/qsQMq2Urf8x
uDKtmm7AdTHq6DeULVxmHj2386u6Kel1IlZtZNllQj/WW4uDM2EZQ78=
-----END CERTIFICATE-----
Binary file not shown.
+28
View File
@@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
-----END CERTIFICATE-----
+31
View File
@@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIFAzCCAusCAQAwbTEYMBYGA1UEAxMPc3NsLmdlbGFyZWgueHl6MRAwDgYDVQQK
DAdQcml2YXRlMREwDwYDVQQLDAhJVCBEZXB0LjETMBEGA1UEBwwKV2FzaGluZ3Rv
bjEKMAgGA1UECAwBLTELMAkGA1UEBgwCVVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQCpRBOFXcI5ASP5gAicJwHwBmBpNXYplI7PsefCZ0b1WXzEEOX+
xBguHs19q2vYAp4Ab/lOPD9+/LjAksWhgJp8jTtr8VnIs7+SufondvkTJR5Fx5aw
io9U2q6lHtltNhnbC4gQVykwY4gvtXV0c7WVV7nXG2oG8324ykCKBD20flWAa/Fi
bC/xaWXFwthsdrgTtO5rSvlkBqNlKWThOuZtMoO+P/xMpC/ocrej0BL852s0l46V
SvpQUtGMDzitgkEU+5C1GlQiBX/jui4PCNRIBBpXXPdcYaKfR9ZwVDCMN8XNBTKO
XdeX+/fsjofdZg1mNRfNkYnS6PGWjw4A7Mv4i+za0+6ZUKgAlUilqkar12CA2xdf
c/bq1asr55hZal7bLn+CL+SQp56h0OPOdq5vcObHOplZ8opIZsTwAR7t3KQbVg2I
cqMOBUF4tBwHn4vLzcOBNTuZcwnCVID74/7L7AkZ/2IEh2atmxBmQrHMwIixxhmi
KqbDXJ6GTk2DdTlNJ6ix2NFhoMsdis7A1ktN/qehHJuUBYdgzszv5qBf3bK+9O8B
KYLCq0GfbHL3JPQQ5HN8As6B/qgI3BZVnZ0JPvP+WpXYMQWHjlIBN1eIBgmGIIBD
VS8Ipx/P3PrpqWJIav/0hk33/6OZdLtYafxn/1ieU2mSpJ0awE54uYZEbQIDAQAB
oFEwTwYJKoZIhvcNAQkOMUIwQDA+BgNVHREENzA1gg9zc2wuZ2VsYXJlaC54eXqC
EHNzbDEuZ2VsYXJlaC54eXqCEHNzbDIuZ2VsYXJlaC54eXowDQYJKoZIhvcNAQEL
BQADggIBAA3xerDVnLk8KAs7ooHWKiQNdM5dUvWgPbMehUxtaZCIA6IraR1qZDfj
4+zvumBOPRdbTYbts1xfbdt+T4B+liu+Zkz+zfYJ+pGglDfe9G0oF8fldIfwUzT5
v6+VF5wiR6mT3F8pUpLqJd6PMHFlb+WTPKZjYVoBDKQ0G2HLSAfHsocaahPvz9Zg
Cz+4FraCk7wlxAG0nzwycQ1NhDpEjQ7Q0UQd23SNWCGc07ZYCfwPLJKCCR0MDhuL
J20rZ+PY+7zWXBLULG49mid1uSpp7XmOcIIj0oFvyv0PmaUOWgvm6zr6nBoKALKW
Qo0dQhY0ODP19uu/NmcS5T97BW6yWti8c9tWJmeMu9WrO8wMyY8DXID0dgdGo9a7
UJo227ZoyqjUHSsrqtP9W8rjok7XRLgxUyRPw1boqAyBnY474v6LUc/VEQNv8mBk
u6HJkWD2dWlAaClpzD37PPPOvuQ8BOiaSI6xrL3+VdfD0Q6GE6jaUuhe+KPwv69N
v+/ErQQXHP08WeaXMHV9yEOtMmVyRvY+9rVIS9HI6cuXLTcMN8cI8TW5f+0IJ/ll
RCY+5Pts++6ajIFC6JwaXBc4mjH8fngHrZByErN3wx0vrPXxs+oXba+ohtnAupNf
wA9qIxSKLJtV/xDxHEqyplX0ke7szhbHzOE1ENgPMeNH0/lDmH1F
-----END CERTIFICATE REQUEST-----
+52
View File
@@ -0,0 +1,52 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----
@@ -0,0 +1,52 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----
+5 -1
View File
@@ -14,6 +14,10 @@ let package = Package(
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
// ServerSecurity: common headers, definitions and protocols
.package(url: "https://github.com/swift-server/security.git", from: "0.0.0"),
// TLSService: implementation of ServerSecurity using OpenSSL and SecureTransport
.package(url: "https://github.com/swift-server/TLSService.git", from: "0.20.3"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
@@ -23,7 +27,7 @@ let package = Package(
dependencies: []),
.target(
name: "HTTP",
dependencies: ["CHTTPParser"]),
dependencies: ["CHTTPParser", "ServerSecurity", "TLSService"]),
.testTarget(
name: "HTTPTests",
dependencies: ["HTTP"]),
+13 -4
View File
@@ -1,6 +1,8 @@
# Swift Server Project HTTP APIs
This is an early implementation of the Swift Server Project's HTTP APIs. This provides simple HTTP server on which rich web application frameworks can be built.
:warning: This project is unmaintained experimental legacy code. It has been obsoleted by [SwiftNIO](https://github.com/apple/swift-nio) which contains the recommended HTTP API of the [Swift Server Work Group](https://swift.org/server/).
It remains here for historical interest only.
## Getting Started
@@ -22,7 +24,7 @@ func hello(request: HTTPRequest, response: HTTPResponseWriter ) -> HTTPBodyProce
let server = HTTPServer()
try! server.start(port: 8080, handler: hello)
CFRunLoopRun()
RunLoop.current.run()
```
The `hello()` function receives a `HTTPRequest` that describes the request and a `HTTPResponseWriter` used to write a response.
@@ -56,7 +58,7 @@ func echo(request: HTTPRequest, response: HTTPResponseWriter ) -> HTTPBodyProces
let server = HTTPServer()
try! server.start(port: 8080, handler: echo)
CFRunLoopRun()
RunLoop.current.run()
```
As the Echo server needs to process the request body data and return it in the reponse, the `echo()` function returns a `.processBody` closure. This closure is called with `.chunk` when data is available for processing from the request, and `.end` when no more data is available.
@@ -68,7 +70,14 @@ When the response is complete, `response.done()` should be called.
Full Jazzy documentation of the API is available here:
<https://swift-server.github.io/http/>
## Contributing
### Feedback
We are actively seeking feedback on this prototype and your comments are extremely valuable. If you have any comments on the API design, the implementation, or any other aspects of this project, please email the [`swift-server-dev`](https://lists.swift.org/mailman/listinfo/swift-server-dev) mailing list.
### Writing Code
We also welcome code contributions. If you are developing on macOS, you may want to work within Xcode. This project uses the [Swift Package Manager](https://swift.org/package-manager/). To work on this project within Xcode you can run the Swift Package Manager command `swift package generate-xcodeproj` to generate an `.xcodeproj` to work on within Xcode.
## Acknowledgements
This project is based on an inital proposal from @weissi on the swift-server-dev mailing list:
<https://lists.swift.org/pipermail/swift-server-dev/Week-of-Mon-20170403/000422.html>
+68
View File
@@ -0,0 +1,68 @@
# Authors ordered by first contribution.
Ryan Dahl <ry@tinyclouds.org>
Jeremy Hinegardner <jeremy@hinegardner.org>
Sergey Shepelev <temotor@gmail.com>
Joe Damato <ice799@gmail.com>
tomika <tomika_nospam@freemail.hu>
Phoenix Sol <phoenix@burninglabs.com>
Cliff Frey <cliff@meraki.com>
Ewen Cheslack-Postava <ewencp@cs.stanford.edu>
Santiago Gala <sgala@apache.org>
Tim Becker <tim.becker@syngenio.de>
Jeff Terrace <jterrace@gmail.com>
Ben Noordhuis <info@bnoordhuis.nl>
Nathan Rajlich <nathan@tootallnate.net>
Mark Nottingham <mnot@mnot.net>
Aman Gupta <aman@tmm1.net>
Tim Becker <tim.becker@kuriositaet.de>
Sean Cunningham <sean.cunningham@mandiant.com>
Peter Griess <pg@std.in>
Salman Haq <salman.haq@asti-usa.com>
Cliff Frey <clifffrey@gmail.com>
Jon Kolb <jon@b0g.us>
Fouad Mardini <f.mardini@gmail.com>
Paul Querna <pquerna@apache.org>
Felix Geisendörfer <felix@debuggable.com>
koichik <koichik@improvement.jp>
Andre Caron <andre.l.caron@gmail.com>
Ivo Raisr <ivosh@ivosh.net>
James McLaughlin <jamie@lacewing-project.org>
David Gwynne <loki@animata.net>
Thomas LE ROUX <thomas@november-eleven.fr>
Randy Rizun <rrizun@ortivawireless.com>
Andre Louis Caron <andre.louis.caron@usherbrooke.ca>
Simon Zimmermann <simonz05@gmail.com>
Erik Dubbelboer <erik@dubbelboer.com>
Martell Malone <martellmalone@gmail.com>
Bertrand Paquet <bpaquet@octo.com>
BogDan Vatra <bogdan@kde.org>
Peter Faiman <peter@thepicard.org>
Corey Richardson <corey@octayn.net>
Tóth Tamás <tomika_nospam@freemail.hu>
Cam Swords <cam.swords@gmail.com>
Chris Dickinson <christopher.s.dickinson@gmail.com>
Uli Köhler <ukoehler@btronik.de>
Charlie Somerville <charlie@charliesomerville.com>
Patrik Stutz <patrik.stutz@gmail.com>
Fedor Indutny <fedor.indutny@gmail.com>
runner <runner.mei@gmail.com>
Alexis Campailla <alexis@janeasystems.com>
David Wragg <david@wragg.org>
Vinnie Falco <vinnie.falco@gmail.com>
Alex Butum <alexbutum@linux.com>
Rex Feng <rexfeng@gmail.com>
Alex Kocharin <alex@kocharin.ru>
Mark Koopman <markmontymark@yahoo.com>
Helge Heß <me@helgehess.eu>
Alexis La Goutte <alexis.lagoutte@gmail.com>
George Miroshnykov <george.miroshnykov@gmail.com>
Maciej Małecki <me@mmalecki.com>
Marc O'Morain <github.com@marcomorain.com>
Jeff Pinner <jpinner@twitter.com>
Timothy J Fontaine <tjfontaine@gmail.com>
Akagi201 <akagi201@gmail.com>
Romain Giraud <giraud.romain@gmail.com>
Jay Satiro <raysatiro@yahoo.com>
Arne Steen <Arne.Steen@gmx.de>
Kjell Schubert <kjell.schubert@gmail.com>
Olivier Mengué <dolmen@cpan.org>
+23
View File
@@ -0,0 +1,23 @@
http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright
Igor Sysoev.
Additional changes are licensed under the same terms as NGINX and
copyright Joyent, Inc. and other Node contributors. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
File diff suppressed because it is too large Load Diff
+262 -331
View File
@@ -24,406 +24,337 @@
extern "C" {
#endif
/* Also update SONAME in the Makefile whenever you change these. */
/* Also update SONAME in the Makefile whenever you change these. */
#define HTTP_PARSER_VERSION_MAJOR 2
#define HTTP_PARSER_VERSION_MINOR 7
#define HTTP_PARSER_VERSION_PATCH 1
#include <stddef.h>
#include <sys/types.h>
#if defined(_WIN32) && !defined(__MINGW32__) && \
(!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__)
(!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__)
#include <BaseTsd.h>
typedef __int8 int8_t;
typedef unsigned __int8 uint8_t;
typedef __int16 int16_t;
typedef unsigned __int16 uint16_t;
typedef __int32 int32_t;
typedef unsigned __int32 uint32_t;
typedef __int64 int64_t;
typedef unsigned __int64 uint64_t;
#include <stddef.h>
typedef __int8 int8_t;
typedef unsigned __int8 uint8_t;
typedef __int16 int16_t;
typedef unsigned __int16 uint16_t;
typedef __int32 int32_t;
typedef unsigned __int32 uint32_t;
typedef __int64 int64_t;
typedef unsigned __int64 uint64_t;
#else
#include <stdint.h>
#endif
/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
* faster
*/
/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
* faster
*/
#ifndef HTTP_PARSER_STRICT
# define HTTP_PARSER_STRICT 1
#endif
/* Maximium header size allowed. If the macro is not defined
* before including this header then the default is used. To
* change the maximum header size, define the macro in the build
* environment (e.g. -DHTTP_MAX_HEADER_SIZE=<value>). To remove
* the effective limit on the size of the header, define the macro
* to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff)
*/
/* Maximium header size allowed. If the macro is not defined
* before including this header then the default is used. To
* change the maximum header size, define the macro in the build
* environment (e.g. -DHTTP_MAX_HEADER_SIZE=<value>). To remove
* the effective limit on the size of the header, define the macro
* to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff)
*/
#ifndef HTTP_MAX_HEADER_SIZE
# define HTTP_MAX_HEADER_SIZE (80*1024)
#endif
typedef struct http_parser http_parser;
typedef struct http_parser_settings http_parser_settings;
typedef struct http_parser http_parser;
typedef struct http_parser_settings http_parser_settings;
/* Callbacks should return non-zero to indicate an error. The parser will
* then halt execution.
*
* The one exception is on_headers_complete. In a HTTP_RESPONSE parser
* returning '1' from on_headers_complete will tell the parser that it
* should not expect a body. This is used when receiving a response to a
* HEAD request which may contain 'Content-Length' or 'Transfer-Encoding:
* chunked' headers that indicate the presence of a body.
*
* Returning `2` from on_headers_complete will tell parser that it should not
* expect neither a body nor any futher responses on this connection. This is
* useful for handling responses to a CONNECT request which may not contain
* `Upgrade` or `Connection: upgrade` headers.
*
* http_data_cb does not return data chunks. It will be called arbitrarily
* many times for each string. E.G. you might get 10 callbacks for "on_url"
* each providing just a few characters more data.
*/
typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
typedef int (*http_cb) (http_parser*);
/* Callbacks should return non-zero to indicate an error. The parser will
* then halt execution.
*
* The one exception is on_headers_complete. In a HTTP_RESPONSE parser
* returning '1' from on_headers_complete will tell the parser that it
* should not expect a body. This is used when receiving a response to a
* HEAD request which may contain 'Content-Length' or 'Transfer-Encoding:
* chunked' headers that indicate the presence of a body.
*
* Returning `2` from on_headers_complete will tell parser that it should not
* expect neither a body nor any futher responses on this connection. This is
* useful for handling responses to a CONNECT request which may not contain
* `Upgrade` or `Connection: upgrade` headers.
*
* http_data_cb does not return data chunks. It will be called arbitrarily
* many times for each string. E.G. you might get 10 callbacks for "on_url"
* each providing just a few characters more data.
*/
typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
typedef int (*http_cb) (http_parser*);
/* Status Codes */
#define HTTP_STATUS_MAP(XX) \
XX(100, CONTINUE, Continue) \
XX(101, SWITCHING_PROTOCOLS, Switching Protocols) \
XX(102, PROCESSING, Processing) \
XX(200, OK, OK) \
XX(201, CREATED, Created) \
XX(202, ACCEPTED, Accepted) \
XX(203, NON_AUTHORITATIVE_INFORMATION, Non-Authoritative Information) \
XX(204, NO_CONTENT, No Content) \
XX(205, RESET_CONTENT, Reset Content) \
XX(206, PARTIAL_CONTENT, Partial Content) \
XX(207, MULTI_STATUS, Multi-Status) \
XX(208, ALREADY_REPORTED, Already Reported) \
XX(226, IM_USED, IM Used) \
XX(300, MULTIPLE_CHOICES, Multiple Choices) \
XX(301, MOVED_PERMANENTLY, Moved Permanently) \
XX(302, FOUND, Found) \
XX(303, SEE_OTHER, See Other) \
XX(304, NOT_MODIFIED, Not Modified) \
XX(305, USE_PROXY, Use Proxy) \
XX(307, TEMPORARY_REDIRECT, Temporary Redirect) \
XX(308, PERMANENT_REDIRECT, Permanent Redirect) \
XX(400, BAD_REQUEST, Bad Request) \
XX(401, UNAUTHORIZED, Unauthorized) \
XX(402, PAYMENT_REQUIRED, Payment Required) \
XX(403, FORBIDDEN, Forbidden) \
XX(404, NOT_FOUND, Not Found) \
XX(405, METHOD_NOT_ALLOWED, Method Not Allowed) \
XX(406, NOT_ACCEPTABLE, Not Acceptable) \
XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required) \
XX(408, REQUEST_TIMEOUT, Request Timeout) \
XX(409, CONFLICT, Conflict) \
XX(410, GONE, Gone) \
XX(411, LENGTH_REQUIRED, Length Required) \
XX(412, PRECONDITION_FAILED, Precondition Failed) \
XX(413, PAYLOAD_TOO_LARGE, Payload Too Large) \
XX(414, URI_TOO_LONG, URI Too Long) \
XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type) \
XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable) \
XX(417, EXPECTATION_FAILED, Expectation Failed) \
XX(421, MISDIRECTED_REQUEST, Misdirected Request) \
XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity) \
XX(423, LOCKED, Locked) \
XX(424, FAILED_DEPENDENCY, Failed Dependency) \
XX(426, UPGRADE_REQUIRED, Upgrade Required) \
XX(428, PRECONDITION_REQUIRED, Precondition Required) \
XX(429, TOO_MANY_REQUESTS, Too Many Requests) \
XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \
XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons) \
XX(500, INTERNAL_SERVER_ERROR, Internal Server Error) \
XX(501, NOT_IMPLEMENTED, Not Implemented) \
XX(502, BAD_GATEWAY, Bad Gateway) \
XX(503, SERVICE_UNAVAILABLE, Service Unavailable) \
XX(504, GATEWAY_TIMEOUT, Gateway Timeout) \
XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported) \
XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates) \
XX(507, INSUFFICIENT_STORAGE, Insufficient Storage) \
XX(508, LOOP_DETECTED, Loop Detected) \
XX(510, NOT_EXTENDED, Not Extended) \
XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) \
enum http_status
{
#define XX(num, name, string) HTTP_STATUS_##name = num,
HTTP_STATUS_MAP(XX)
#undef XX
};
/* Request Methods */
/* Request Methods */
#define HTTP_METHOD_MAP(XX) \
XX(0, DELETE, DELETE) \
XX(1, GET, GET) \
XX(2, HEAD, HEAD) \
XX(3, POST, POST) \
XX(4, PUT, PUT) \
/* pathological */ \
XX(5, CONNECT, CONNECT) \
XX(6, OPTIONS, OPTIONS) \
XX(7, TRACE, TRACE) \
/* WebDAV */ \
XX(8, COPY, COPY) \
XX(9, LOCK, LOCK) \
XX(10, MKCOL, MKCOL) \
XX(11, MOVE, MOVE) \
XX(12, PROPFIND, PROPFIND) \
XX(13, PROPPATCH, PROPPATCH) \
XX(14, SEARCH, SEARCH) \
XX(15, UNLOCK, UNLOCK) \
XX(16, BIND, BIND) \
XX(17, REBIND, REBIND) \
XX(18, UNBIND, UNBIND) \
XX(19, ACL, ACL) \
/* subversion */ \
XX(20, REPORT, REPORT) \
XX(21, MKACTIVITY, MKACTIVITY) \
XX(22, CHECKOUT, CHECKOUT) \
XX(23, MERGE, MERGE) \
/* upnp */ \
XX(24, MSEARCH, M-SEARCH) \
XX(25, NOTIFY, NOTIFY) \
XX(26, SUBSCRIBE, SUBSCRIBE) \
XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \
/* RFC-5789 */ \
XX(28, PATCH, PATCH) \
XX(29, PURGE, PURGE) \
/* CalDAV */ \
XX(30, MKCALENDAR, MKCALENDAR) \
/* RFC-2068, section 19.6.1.2 */ \
XX(31, LINK, LINK) \
XX(32, UNLINK, UNLINK) \
XX(0, DELETE, DELETE) \
XX(1, GET, GET) \
XX(2, HEAD, HEAD) \
XX(3, POST, POST) \
XX(4, PUT, PUT) \
/* pathological */ \
XX(5, CONNECT, CONNECT) \
XX(6, OPTIONS, OPTIONS) \
XX(7, TRACE, TRACE) \
/* WebDAV */ \
XX(8, COPY, COPY) \
XX(9, LOCK, LOCK) \
XX(10, MKCOL, MKCOL) \
XX(11, MOVE, MOVE) \
XX(12, PROPFIND, PROPFIND) \
XX(13, PROPPATCH, PROPPATCH) \
XX(14, SEARCH, SEARCH) \
XX(15, UNLOCK, UNLOCK) \
XX(16, BIND, BIND) \
XX(17, REBIND, REBIND) \
XX(18, UNBIND, UNBIND) \
XX(19, ACL, ACL) \
/* subversion */ \
XX(20, REPORT, REPORT) \
XX(21, MKACTIVITY, MKACTIVITY) \
XX(22, CHECKOUT, CHECKOUT) \
XX(23, MERGE, MERGE) \
/* upnp */ \
XX(24, MSEARCH, M-SEARCH) \
XX(25, NOTIFY, NOTIFY) \
XX(26, SUBSCRIBE, SUBSCRIBE) \
XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \
/* RFC-5789 */ \
XX(28, PATCH, PATCH) \
XX(29, PURGE, PURGE) \
/* CalDAV */ \
XX(30, MKCALENDAR, MKCALENDAR) \
/* RFC-2068, section 19.6.1.2 */ \
XX(31, LINK, LINK) \
XX(32, UNLINK, UNLINK) \
enum http_method
{
enum http_method
{
#define XX(num, name, string) HTTP_##name = num,
HTTP_METHOD_MAP(XX)
HTTP_METHOD_MAP(XX)
#undef XX
};
};
enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };
enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };
/* Flag values for http_parser.flags field */
enum flags
{ F_CHUNKED = 1 << 0
, F_CONNECTION_KEEP_ALIVE = 1 << 1
, F_CONNECTION_CLOSE = 1 << 2
, F_CONNECTION_UPGRADE = 1 << 3
, F_TRAILING = 1 << 4
, F_UPGRADE = 1 << 5
, F_SKIPBODY = 1 << 6
, F_CONTENTLENGTH = 1 << 7
};
/* Flag values for http_parser.flags field */
enum flags
{ F_CHUNKED = 1 << 0
, F_CONNECTION_KEEP_ALIVE = 1 << 1
, F_CONNECTION_CLOSE = 1 << 2
, F_CONNECTION_UPGRADE = 1 << 3
, F_TRAILING = 1 << 4
, F_UPGRADE = 1 << 5
, F_SKIPBODY = 1 << 6
, F_CONTENTLENGTH = 1 << 7
};
/* Map for errno-related constants
*
* The provided argument should be a macro that takes 2 arguments.
*/
/* Map for errno-related constants
*
* The provided argument should be a macro that takes 2 arguments.
*/
#define HTTP_ERRNO_MAP(XX) \
/* No error */ \
XX(OK, "success") \
\
/* Callback-related errors */ \
XX(CB_message_begin, "the on_message_begin callback failed") \
XX(CB_url, "the on_url callback failed") \
XX(CB_header_field, "the on_header_field callback failed") \
XX(CB_header_value, "the on_header_value callback failed") \
XX(CB_headers_complete, "the on_headers_complete callback failed") \
XX(CB_body, "the on_body callback failed") \
XX(CB_message_complete, "the on_message_complete callback failed") \
XX(CB_status, "the on_status callback failed") \
XX(CB_chunk_header, "the on_chunk_header callback failed") \
XX(CB_chunk_complete, "the on_chunk_complete callback failed") \
\
/* Parsing-related errors */ \
XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \
XX(HEADER_OVERFLOW, \
"too many header bytes seen; overflow detected") \
XX(CLOSED_CONNECTION, \
"data received after completed connection: close message") \
XX(INVALID_VERSION, "invalid HTTP version") \
XX(INVALID_STATUS, "invalid HTTP status code") \
XX(INVALID_METHOD, "invalid HTTP method") \
XX(INVALID_URL, "invalid URL") \
XX(INVALID_HOST, "invalid host") \
XX(INVALID_PORT, "invalid port") \
XX(INVALID_PATH, "invalid path") \
XX(INVALID_QUERY_STRING, "invalid query string") \
XX(INVALID_FRAGMENT, "invalid fragment") \
XX(LF_EXPECTED, "LF character expected") \
XX(INVALID_HEADER_TOKEN, "invalid character in header") \
XX(INVALID_CONTENT_LENGTH, \
"invalid character in content-length header") \
XX(UNEXPECTED_CONTENT_LENGTH, \
"unexpected content-length header") \
XX(INVALID_CHUNK_SIZE, \
"invalid character in chunk size header") \
XX(INVALID_CONSTANT, "invalid constant string") \
XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\
XX(STRICT, "strict mode assertion failed") \
XX(PAUSED, "parser is paused") \
XX(UNKNOWN, "an unknown error occurred")
/* No error */ \
XX(OK, "success") \
\
/* Callback-related errors */ \
XX(CB_message_begin, "the on_message_begin callback failed") \
XX(CB_url, "the on_url callback failed") \
XX(CB_header_field, "the on_header_field callback failed") \
XX(CB_header_value, "the on_header_value callback failed") \
XX(CB_headers_complete, "the on_headers_complete callback failed") \
XX(CB_body, "the on_body callback failed") \
XX(CB_message_complete, "the on_message_complete callback failed") \
XX(CB_status, "the on_status callback failed") \
XX(CB_chunk_header, "the on_chunk_header callback failed") \
XX(CB_chunk_complete, "the on_chunk_complete callback failed") \
\
/* Parsing-related errors */ \
XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \
XX(HEADER_OVERFLOW, \
"too many header bytes seen; overflow detected") \
XX(CLOSED_CONNECTION, \
"data received after completed connection: close message") \
XX(INVALID_VERSION, "invalid HTTP version") \
XX(INVALID_STATUS, "invalid HTTP status code") \
XX(INVALID_METHOD, "invalid HTTP method") \
XX(INVALID_URL, "invalid URL") \
XX(INVALID_HOST, "invalid host") \
XX(INVALID_PORT, "invalid port") \
XX(INVALID_PATH, "invalid path") \
XX(INVALID_QUERY_STRING, "invalid query string") \
XX(INVALID_FRAGMENT, "invalid fragment") \
XX(LF_EXPECTED, "LF character expected") \
XX(INVALID_HEADER_TOKEN, "invalid character in header") \
XX(INVALID_CONTENT_LENGTH, \
"invalid character in content-length header") \
XX(UNEXPECTED_CONTENT_LENGTH, \
"unexpected content-length header") \
XX(INVALID_CHUNK_SIZE, \
"invalid character in chunk size header") \
XX(INVALID_CONSTANT, "invalid constant string") \
XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\
XX(STRICT, "strict mode assertion failed") \
XX(PAUSED, "parser is paused") \
XX(UNKNOWN, "an unknown error occurred")
/* Define HPE_* values for each errno value above */
/* Define HPE_* values for each errno value above */
#define HTTP_ERRNO_GEN(n, s) HPE_##n,
enum http_errno {
HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)
};
enum http_errno {
HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)
};
#undef HTTP_ERRNO_GEN
/* Get an http_errno value from an http_parser */
/* Get an http_errno value from an http_parser */
#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno)
struct http_parser {
/** PRIVATE **/
unsigned int type : 2; /* enum http_parser_type */
unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */
unsigned int state : 7; /* enum state from http_parser.c */
unsigned int header_state : 7; /* enum header_state from http_parser.c */
unsigned int index : 7; /* index into current matcher */
unsigned int lenient_http_headers : 1;
struct http_parser {
/** PRIVATE **/
unsigned int type : 2; /* enum http_parser_type */
unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */
unsigned int state : 7; /* enum state from http_parser.c */
unsigned int header_state : 7; /* enum header_state from http_parser.c */
unsigned int index : 7; /* index into current matcher */
unsigned int lenient_http_headers : 1;
uint32_t nread; /* # bytes read in various scenarios */
uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */
uint32_t nread; /* # bytes read in various scenarios */
uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */
/** READ-ONLY **/
unsigned short http_major;
unsigned short http_minor;
unsigned int status_code : 16; /* responses only */
unsigned int method : 8; /* requests only */
unsigned int http_errno : 7;
/** READ-ONLY **/
unsigned short http_major;
unsigned short http_minor;
unsigned int status_code : 16; /* responses only */
unsigned int method : 8; /* requests only */
unsigned int http_errno : 7;
/* 1 = Upgrade header was present and the parser has exited because of that.
* 0 = No upgrade header present.
* Should be checked when http_parser_execute() returns in addition to
* error checking.
*/
unsigned int upgrade : 1;
/* 1 = Upgrade header was present and the parser has exited because of that.
* 0 = No upgrade header present.
* Should be checked when http_parser_execute() returns in addition to
* error checking.
*/
unsigned int upgrade : 1;
/** PUBLIC **/
void *data; /* A pointer to get hook to the "connection" or "socket" object */
};
/** PUBLIC **/
void *data; /* A pointer to get hook to the "connection" or "socket" object */
};
struct http_parser_settings {
http_cb on_message_begin;
http_data_cb on_url;
http_data_cb on_status;
http_data_cb on_header_field;
http_data_cb on_header_value;
http_cb on_headers_complete;
http_data_cb on_body;
http_cb on_message_complete;
/* When on_chunk_header is called, the current chunk length is stored
* in parser->content_length.
*/
http_cb on_chunk_header;
http_cb on_chunk_complete;
};
struct http_parser_settings {
http_cb on_message_begin;
http_data_cb on_url;
http_data_cb on_status;
http_data_cb on_header_field;
http_data_cb on_header_value;
http_cb on_headers_complete;
http_data_cb on_body;
http_cb on_message_complete;
/* When on_chunk_header is called, the current chunk length is stored
* in parser->content_length.
*/
http_cb on_chunk_header;
http_cb on_chunk_complete;
};
enum http_parser_url_fields
{ UF_SCHEMA = 0
, UF_HOST = 1
, UF_PORT = 2
, UF_PATH = 3
, UF_QUERY = 4
, UF_FRAGMENT = 5
, UF_USERINFO = 6
, UF_MAX = 7
};
enum http_parser_url_fields
{ UF_SCHEMA = 0
, UF_HOST = 1
, UF_PORT = 2
, UF_PATH = 3
, UF_QUERY = 4
, UF_FRAGMENT = 5
, UF_USERINFO = 6
, UF_MAX = 7
};
/* Result structure for http_parser_parse_url().
*
* Callers should index into field_data[] with UF_* values iff field_set
* has the relevant (1 << UF_*) bit set. As a courtesy to clients (and
* because we probably have padding left over), we convert any port to
* a uint16_t.
*/
struct http_parser_url {
uint16_t field_set; /* Bitmask of (1 << UF_*) values */
uint16_t port; /* Converted UF_PORT string */
/* Result structure for http_parser_parse_url().
*
* Callers should index into field_data[] with UF_* values iff field_set
* has the relevant (1 << UF_*) bit set. As a courtesy to clients (and
* because we probably have padding left over), we convert any port to
* a uint16_t.
*/
struct http_parser_url {
uint16_t field_set; /* Bitmask of (1 << UF_*) values */
uint16_t port; /* Converted UF_PORT string */
struct {
uint16_t off; /* Offset into buffer in which field starts */
uint16_t len; /* Length of run in buffer */
} field_data[UF_MAX];
};
struct {
uint16_t off; /* Offset into buffer in which field starts */
uint16_t len; /* Length of run in buffer */
} field_data[UF_MAX];
};
/* Returns the library version. Bits 16-23 contain the major version number,
* bits 8-15 the minor version number and bits 0-7 the patch level.
* Usage example:
*
* unsigned long version = http_parser_version();
* unsigned major = (version >> 16) & 255;
* unsigned minor = (version >> 8) & 255;
* unsigned patch = version & 255;
* printf("http_parser v%u.%u.%u\n", major, minor, patch);
*/
unsigned long http_parser_version(void);
/* Returns the library version. Bits 16-23 contain the major version number,
* bits 8-15 the minor version number and bits 0-7 the patch level.
* Usage example:
*
* unsigned long version = http_parser_version();
* unsigned major = (version >> 16) & 255;
* unsigned minor = (version >> 8) & 255;
* unsigned patch = version & 255;
* printf("http_parser v%u.%u.%u\n", major, minor, patch);
*/
unsigned long http_parser_version(void);
void http_parser_init(http_parser *parser, enum http_parser_type type);
void http_parser_init(http_parser *parser, enum http_parser_type type);
/* Initialize http_parser_settings members to 0
*/
void http_parser_settings_init(http_parser_settings *settings);
/* Initialize http_parser_settings members to 0
*/
void http_parser_settings_init(http_parser_settings *settings);
/* Executes the parser. Returns number of parsed bytes. Sets
* `parser->http_errno` on error. */
size_t http_parser_execute(http_parser *parser,
const http_parser_settings *settings,
const char *data,
size_t len);
/* Executes the parser. Returns number of parsed bytes. Sets
* `parser->http_errno` on error. */
size_t http_parser_execute(http_parser *parser,
const http_parser_settings *settings,
const char *data,
size_t len);
/* If http_should_keep_alive() in the on_headers_complete or
* on_message_complete callback returns 0, then this should be
* the last message on the connection.
* If you are the server, respond with the "Connection: close" header.
* If you are the client, close the connection.
*/
int http_should_keep_alive(const http_parser *parser);
/* If http_should_keep_alive() in the on_headers_complete or
* on_message_complete callback returns 0, then this should be
* the last message on the connection.
* If you are the server, respond with the "Connection: close" header.
* If you are the client, close the connection.
*/
int http_should_keep_alive(const http_parser *parser);
/* Returns a string version of the HTTP method. */
const char *http_method_str(enum http_method m);
/* Returns a string version of the HTTP method. */
const char *http_method_str(enum http_method m);
/* Return a string name of the given error */
const char *http_errno_name(enum http_errno err);
/* Return a string name of the given error */
const char *http_errno_name(enum http_errno err);
/* Return a string description of the given error */
const char *http_errno_description(enum http_errno err);
/* Return a string description of the given error */
const char *http_errno_description(enum http_errno err);
/* Initialize all http_parser_url members to 0 */
void http_parser_url_init(struct http_parser_url *u);
/* Initialize all http_parser_url members to 0 */
void http_parser_url_init(struct http_parser_url *u);
/* Parse a URL; return nonzero on failure */
int http_parser_parse_url(const char *buf, size_t buflen,
int is_connect,
struct http_parser_url *u);
/* Parse a URL; return nonzero on failure */
int http_parser_parse_url(const char *buf, size_t buflen,
int is_connect,
struct http_parser_url *u);
/* Pause or un-pause the parser; a nonzero value pauses */
void http_parser_pause(http_parser *parser, int paused);
/* Pause or un-pause the parser; a nonzero value pauses */
void http_parser_pause(http_parser *parser, int paused);
/* Checks if this is the final chunk of the body. */
int http_body_is_final(const http_parser *parser);
/* Checks if this is the final chunk of the body. */
int http_body_is_final(const http_parser *parser);
#ifdef __cplusplus
}
+2 -2
View File
@@ -36,7 +36,7 @@ public struct HTTPHeaders {
}
}
extension HTTPHeaders : ExpressibleByDictionaryLiteral {
extension HTTPHeaders: ExpressibleByDictionaryLiteral {
/// Creates HTTP headers.
public init(dictionaryLiteral: (Name, String)...) {
storage = [:]
@@ -92,7 +92,7 @@ extension HTTPHeaders {
}
}
extension HTTPHeaders : Sequence {
extension HTTPHeaders: Sequence {
/// :nodoc:
public func makeIterator() -> AnyIterator<(name: Name, value: String)> {
if let original = original {
+4 -4
View File
@@ -40,7 +40,7 @@ extension HTTPMethod {
/// LOCK method.
public static let lock = HTTPMethod("LOCK")
/// MKCOL method.
public static let mkol = HTTPMethod("MKCOL")
public static let mkcol = HTTPMethod("MKCOL")
/// MOVE method.
public static let move = HTTPMethod("MOVE")
/// PROPFIND method.
@@ -87,7 +87,7 @@ extension HTTPMethod {
public static let unlink = HTTPMethod("UNLINK")
}
extension HTTPMethod : Hashable {
extension HTTPMethod: Hashable {
public var hashValue: Int {
return method.hashValue
}
@@ -103,7 +103,7 @@ extension HTTPMethod : Hashable {
}
}
extension HTTPMethod : ExpressibleByStringLiteral {
extension HTTPMethod: ExpressibleByStringLiteral {
/// :nodoc:
public init(stringLiteral: String) {
self.init(stringLiteral)
@@ -120,7 +120,7 @@ extension HTTPMethod : ExpressibleByStringLiteral {
}
}
extension HTTPMethod : CustomStringConvertible {
extension HTTPMethod: CustomStringConvertible {
/// :nodoc:
public var description: String {
return method
+1 -1
View File
@@ -30,7 +30,7 @@ public typealias HTTPBodyHandler = (HTTPBodyChunk, inout Bool) -> Void
public enum HTTPBodyProcessing {
/// Used to discard the body data associated with the incoming HTTP request
case discardBody
/// Used to process the body data associated with the imcoming HTTP request using a `HTTPBodyHandler`
/// Used to process the body data associated with the incoming HTTP request using a `HTTPBodyHandler`
case processBody(handler: HTTPBodyHandler)
}
+1 -1
View File
@@ -20,7 +20,7 @@ public struct HTTPResponse {
}
/// HTTPResponseWriter provides functions to create an HTTP response
public protocol HTTPResponseWriter : class {
public protocol HTTPResponseWriter: class {
/// Writer function to create the headers for an HTTP response
/// - Parameter status: The status code to include in the HTTP response
/// - Parameter headers: The HTTP headers to include in the HTTP response
+29 -20
View File
@@ -6,35 +6,44 @@
// See http://swift.org/LICENSE.txt for license information
//
/// Definition of an HTTP server.
public protocol HTTPServing : class {
/// Start the HTTP server on the given `port`, using `handler` to process incoming requests
func start(port: Int, handler: @escaping HTTPRequestHandler) throws
/// Stop the server
func stop()
/// The port the server is listening on
var port: Int { get }
/// The number of current connections
var connectionCount: Int { get }
}
import ServerSecurity
/// A basic HTTP server. Currently this is implemented using the PoCSocket
/// abstraction, but the intention is to remove this dependency and reimplement
/// the class using transport APIs provided by the Server APIs working group.
public class HTTPServer: HTTPServing {
private let server = PoCSocketSimpleServer()
public class HTTPServer {
/// Configuration options for creating HTTPServer
open class Options {
/// HTTPServer to be created on a given `port`
/// Note: For Port=0, the kernel assigns a random port. This will cause HTTPServer.port value
/// to diverge from HTTPServer.Options.port
public let port: Int
public let tlsConfig: TLSConfiguration?
/// Create an instance of HTTPServerOptions
public init(onPort: Int = 0, tlsConf: TLSConfiguration? = nil) {
port = onPort
tlsConfig = tlsConf
}
}
public let options: Options
/// To process incoming requests
public let handler: HTTPRequestHandler
private let server = PoCSocketSimpleServer()
/// Create an instance of the server. This needs to be followed with a call to `start(port:handler:)`
public init() {
public init(with newOptions: Options, requestHandler: @escaping HTTPRequestHandler) {
options = newOptions
handler = requestHandler
}
/// Start the HTTP server on the given `port` number, using a `HTTPRequestHandler` to process incoming requests.
public func start(port: Int = 0, handler: @escaping HTTPRequestHandler) throws {
try server.start(port: port, handler: handler)
public func start() throws {
try server.start(port: options.port, tls: options.tlsConfig, handler: handler)
}
/// Stop the server
+69 -9
View File
@@ -10,6 +10,10 @@ import CHTTPParser
import Foundation
import Dispatch
public enum StreamingParserError: Error {
case ConnectionAbortedError
}
/// Class that wraps the CHTTPParser and calls the `HTTPRequestHandler` to get the response
/// :nodoc:
public class StreamingParser: HTTPResponseWriter {
@@ -41,12 +45,35 @@ public class StreamingParser: HTTPResponseWriter {
_keepAliveUntil = newValue
}
}
/// Tracks when we've been told socket has been closed. Needs to have a lock, since if we get confused, bad things happen
private let _abortCalledLock = DispatchSemaphore(value: 1)
private var _abortCalled: Bool = false
internal var abortCalled: Bool {
get {
_abortCalledLock.wait()
defer {
_abortCalledLock.signal()
}
return _abortCalled
}
set {
_abortCalledLock.wait()
defer {
_abortCalledLock.signal()
}
_abortCalled = newValue
}
}
/// Optional delegate that can tell us how many connections are in-flight.
public weak var connectionCounter: CurrentConnectionCounting?
/// Holds the bytes that come from the CHTTPParser until we have enough of them to do something with it
var parserBuffer: Data?
/// Lock for parser buffer.
private let _parserBufferLock = DispatchSemaphore(value: 1)
/// HTTP Parser
var httpParser = http_parser()
@@ -189,6 +216,8 @@ public class StreamingParser: HTTPResponseWriter {
if lastCallBack == currentCallBack {
return false
}
_parserBufferLock.wait()
defer { _parserBufferLock.signal() }
switch lastCallBack {
case .headerFieldReceived:
if let parserBuffer = self.parserBuffer {
@@ -200,7 +229,7 @@ public class StreamingParser: HTTPResponseWriter {
case .headerValueReceived:
if let parserBuffer = self.parserBuffer,
let lastHeaderName = self.lastHeaderName,
let headerValue = String(data:parserBuffer, encoding: .utf8) {
let headerValue = String(data: parserBuffer, encoding: .utf8) {
self.parsedHeaders.append([HTTPHeaders.Name(lastHeaderName): headerValue])
self.lastHeaderName = nil
self.parserBuffer = nil
@@ -217,7 +246,7 @@ public class StreamingParser: HTTPResponseWriter {
if let parserBuffer = self.parserBuffer {
//Under heaptrack, this may appear to leak via _CFGetTSDCreateIfNeeded,
// apparently, that's because it triggers thread metadata to be created
self.parsedURL = String(data:parserBuffer, encoding: .utf8)
self.parsedURL = String(data: parserBuffer, encoding: .utf8)
self.parserBuffer = nil
} else {
print("Missing parserBuffer after \(lastCallBack)")
@@ -274,6 +303,9 @@ public class StreamingParser: HTTPResponseWriter {
func headerFieldReceived(data: UnsafePointer<Int8>?, length: Int) -> Int32 {
processCurrentCallback(.headerFieldReceived)
guard let data = data else { return 0 }
_parserBufferLock.wait()
defer { _parserBufferLock.signal() }
data.withMemoryRebound(to: UInt8.self, capacity: length) { (ptr) -> Void in
if var parserBuffer = parserBuffer {
parserBuffer.append(ptr, count: length)
@@ -287,6 +319,9 @@ public class StreamingParser: HTTPResponseWriter {
func headerValueReceived(data: UnsafePointer<Int8>?, length: Int) -> Int32 {
processCurrentCallback(.headerValueReceived)
guard let data = data else { return 0 }
_parserBufferLock.wait()
defer { _parserBufferLock.signal() }
data.withMemoryRebound(to: UInt8.self, capacity: length) { (ptr) -> Void in
if var parserBuffer = parserBuffer {
parserBuffer.append(ptr, count: length)
@@ -320,7 +355,7 @@ public class StreamingParser: HTTPResponseWriter {
// just passing in a pointer to the internal ivar. But that ivar can't be modified in
// more than one place, so we have to put a semaphore around it to prevent that.
_shouldStopProcessingBodyLock.wait()
handler(.chunk(data: chunk, finishedProcessing: {self._shouldStopProcessingBodyLock.signal()}), &_shouldStopProcessingBody)
handler(.chunk(data: chunk, finishedProcessing: { self._shouldStopProcessingBodyLock.signal() }), &_shouldStopProcessingBody)
case .discardBody:
break
}
@@ -332,6 +367,9 @@ public class StreamingParser: HTTPResponseWriter {
func urlReceived(data: UnsafePointer<Int8>?, length: Int) -> Int32 {
processCurrentCallback(.urlReceived)
guard let data = data else { return 0 }
_parserBufferLock.wait()
defer { _parserBufferLock.signal() }
data.withMemoryRebound(to: UInt8.self, capacity: length) { (ptr) -> Void in
if var parserBuffer = parserBuffer {
parserBuffer.append(ptr, count: length)
@@ -366,10 +404,10 @@ public class StreamingParser: HTTPResponseWriter {
var header = "HTTP/1.1 \(status.code) \(status.reasonPhrase)\r\n"
let isContinue = status == .continue
let isInformational = status.class == .informational
var headers = headers
if !isContinue {
if !isInformational {
adjustHeaders(status: status, headers: &headers)
}
@@ -378,12 +416,16 @@ public class StreamingParser: HTTPResponseWriter {
header += "\(key): \(value)\r\n"
}
header.append("\r\n")
guard !abortCalled else {
completion(.error(StreamingParserError.ConnectionAbortedError))
return
}
// FIXME headers are US-ASCII, anything else should be encoded using [RFC5987] some lines above
// TODO use requested encoding if specified
if let data = header.data(using: .utf8) {
self.parserConnector?.queueSocketWrite(data, completion: completion)
if !isContinue {
if !isInformational {
headersWritten = true
}
} else {
@@ -414,7 +456,6 @@ public class StreamingParser: HTTPResponseWriter {
headers[.transferEncoding] = nil
}
if clientRequestedKeepAlive {
headers[.connection] = "Keep-Alive"
} else {
@@ -452,11 +493,20 @@ public class StreamingParser: HTTPResponseWriter {
} else {
dataToWrite = data.withUnsafeBytes { Data($0) }
}
guard !abortCalled else {
completion(.error(StreamingParserError.ConnectionAbortedError))
return
}
self.parserConnector?.queueSocketWrite(dataToWrite, completion: completion)
}
public func done(completion: @escaping (Result) -> Void) {
guard !abortCalled else {
completion(.error(StreamingParserError.ConnectionAbortedError))
return
}
if isChunked {
let chunkTerminate = "0\r\n\r\n".data(using: .utf8)!
self.parserConnector?.queueSocketWrite(chunkTerminate, completion: completion)
@@ -466,7 +516,9 @@ public class StreamingParser: HTTPResponseWriter {
self.parsedURL = nil
self.parsedHeaders = HTTPHeaders()
self.lastHeaderName = nil
_parserBufferLock.wait()
self.parserBuffer = nil
_parserBufferLock.signal()
self.parsedHTTPMethod = nil
self.parsedHTTPVersion = nil
self.lastCallBack = .idle
@@ -479,15 +531,23 @@ public class StreamingParser: HTTPResponseWriter {
// But since that block was removed, we're calling it directly
if self.clientRequestedKeepAlive {
self.keepAliveUntil = Date(timeIntervalSinceNow: keepAliveTimeout).timeIntervalSinceReferenceDate
guard !abortCalled else {
completion(.error(StreamingParserError.ConnectionAbortedError))
return
}
self.parserConnector?.responseComplete()
} else {
guard !abortCalled else {
completion(.error(StreamingParserError.ConnectionAbortedError))
return
}
self.parserConnector?.responseCompleteCloseWriter()
}
completion(.ok)
}
public func abort() {
fatalError("abort called, not sure what to do with it")
abortCalled = true
}
deinit {
@@ -507,7 +567,7 @@ public protocol ParserConnecting: class {
/// Let the network know that a response is complete, so it can be closed after timeout
func responseComplete()
/// Let the network know that a response is complete and we're ready to close the connection
func responseCompleteCloseWriter()
+6 -5
View File
@@ -20,12 +20,14 @@ public struct HTTPVersion {
}
}
extension HTTPVersion : Hashable {
extension HTTPVersion: Hashable {
/// :nodoc:
public var hashValue: Int {
return (major << 8) | minor
}
}
extension HTTPVersion {
/// :nodoc:
public static func == (lhs: HTTPVersion, rhs: HTTPVersion) -> Bool {
return lhs.major == rhs.major && lhs.minor == rhs.minor
@@ -37,7 +39,7 @@ extension HTTPVersion : Hashable {
}
}
extension HTTPVersion : Comparable {
extension HTTPVersion: Comparable {
/// :nodoc:
public static func < (lhs: HTTPVersion, rhs: HTTPVersion) -> Bool {
if lhs.major != rhs.major {
@@ -46,12 +48,11 @@ extension HTTPVersion : Comparable {
return lhs.minor < rhs.minor
}
}
}
extension HTTPVersion : CustomStringConvertible {
extension HTTPVersion: CustomStringConvertible {
/// :nodoc:
public var description: String {
return "HTTP/" + major.description + "." + minor.description
return "HTTP/\(major).\(minor)"
}
}
+133 -45
View File
@@ -8,6 +8,7 @@
import Foundation
import Dispatch
import ServerSecurity
///:nodoc:
public enum PoCSocketError: Error {
@@ -21,21 +22,55 @@ public enum PoCSocketError: Error {
/// Simple Wrapper around the `socket(2)` functions we need for Proof of Concept testing
/// Intentionally a thin layer over `recv(2)`/`send(2)` so uses the same argument types.
/// Note that no method names here are the same as any system call names.
/// This is because we expect the caller might need functionality we haven't implemented here.
internal class PoCSocket {
/// This is because we expect the caller might need functionality we haven't implemented here.
internal class PoCSocket: ConnectionDelegate {
/// hold the file descriptor for the socket supplied by the OS. `-1` is invalid socket
internal var socketfd: Int32 = -1
/// The TCP port the server is actually listening on. Set after system call completes
internal var listeningPort: Int32 = -1
/// Track state between `listen(2)` and `shutdown(2)`
internal private(set) var isListening = false
private let _isListeningLock = DispatchSemaphore(value: 1)
private var _isListening: Bool = false
internal private(set) var isListening: Bool {
get {
_isListeningLock.wait()
defer {
_isListeningLock.signal()
}
return _isListening
}
set {
_isListeningLock.wait()
defer {
_isListeningLock.signal()
}
_isListening = newValue
}
}
/// Track state between `accept(2)/bind(2)` and `close(2)`
internal private(set) var isConnected = false
private let _isConnectedLock = DispatchSemaphore(value: 1)
private var _isConnected: Bool = false
internal private(set) var isConnected: Bool {
get {
_isConnectedLock.wait()
defer {
_isConnectedLock.signal()
}
return _isConnected
}
set {
_isConnectedLock.wait()
defer {
_isConnectedLock.signal()
}
_isConnected = newValue
}
}
/// track whether a shutdown is in progress so we can suppress error messages
private let _isShuttingDownLock = DispatchSemaphore(value: 1)
private var _isShuttingDown: Bool = false
@@ -56,6 +91,35 @@ internal class PoCSocket {
}
}
/// Delegate that provides the TLS implementation
public var TLSdelegate: TLSServiceDelegate? = nil
/// Return the file descriptor as a connection endpoint for ConnectionDelegate.
public var endpoint: ConnectionType {
get {
return ConnectionType.socket(self.socketfd)
}
}
/// track whether a the socket has already been closed.
private let _hasClosedLock = DispatchSemaphore(value: 1)
private var _hasClosed: Bool = false
private var hasClosed: Bool {
get {
_hasClosedLock.wait()
defer {
_hasClosedLock.signal()
}
return _hasClosed
}
set {
_hasClosedLock.wait()
defer {
_hasClosedLock.signal()
}
_hasClosed = newValue
}
}
/// Call recv(2) with buffer allocated by our caller and return the output
///
/// - Parameters:
@@ -63,7 +127,7 @@ internal class PoCSocket {
/// - maxLength: Max length that can be read. Buffer *must* be at least this big!!!
/// - Returns: Number of bytes read or -1 on failure as per `recv(2)`
/// - Throws: PoCSocketError if sanity checks fail
internal func socketRead(into readBuffer: inout UnsafeMutablePointer<Int8>, maxLength:Int) throws -> Int {
internal func socketRead(into readBuffer: inout UnsafeMutablePointer<Int8>, maxLength: Int) throws -> Int {
if maxLength <= 0 || maxLength > Int(Int32.max) {
throw PoCSocketError.InvalidReadLengthError
}
@@ -76,15 +140,22 @@ internal class PoCSocket {
if readBufferPointer == nil {
throw PoCSocketError.InvalidBufferError
}
//Make sure data isn't re-used
readBuffer.initialize(to: 0x0, count: maxLength)
let read = recv(self.socketfd, readBuffer, maxLength, Int32(0))
let read: Int
if let tls = self.TLSdelegate {
// HTTPS
read = try tls.willReceive(into: readBuffer, bufSize: maxLength)
} else {
// HTTP
read = recv(self.socketfd, readBuffer, maxLength, Int32(0))
}
//Leave this as a local variable to facilitate Setting a Watchpoint in lldb
return read
}
/// Pass buffer passed into to us into send(2).
///
/// - Parameters:
@@ -99,35 +170,48 @@ internal class PoCSocket {
if bufSize < 0 || bufSize > Int(Int32.max) {
throw PoCSocketError.InvalidWriteLengthError
}
//Make sure we weren't handed a nil buffer
// Make sure we weren't handed a nil buffer
let writeBufferPointer: UnsafeRawPointer! = buffer
if writeBufferPointer == nil {
throw PoCSocketError.InvalidBufferError
}
let sent = send(self.socketfd, buffer, Int(bufSize), Int32(0))
let sent: Int
if let tls = self.TLSdelegate {
// HTTPS
sent = try tls.willSend(buffer: buffer, bufSize: Int(bufSize))
} else {
// HTTP
sent = send(self.socketfd, buffer, Int(bufSize), Int32(0))
}
//Leave this as a local variable to facilitate Setting a Watchpoint in lldb
return sent
return sent
}
/// Calls `shutdown(2)` and `close(2)` on a socket
internal func shutdownAndClose() {
self.isShuttingDown = true
if let tls = self.TLSdelegate {
tls.willDestroy()
}
if socketfd < 1 {
//Nothing to do. Maybe it was closed already
return
}
//print("Shutting down socket \(self.socketfd)")
if hasClosed {
//Nothing to do. It was closed already
return
}
if self.isListening || self.isConnected {
//print("Shutting down socket")
_ = shutdown(self.socketfd, Int32(SHUT_RDWR))
self.isListening = false
}
self.isConnected = false
close(self.socketfd)
self.hasClosed = true
}
/// Thin wrapper around `accept(2)`
///
/// - Returns: PoCSocket object for newly connected socket or nil if we've been told to shutdown
@@ -138,14 +222,14 @@ internal class PoCSocket {
}
let retVal = PoCSocket()
var maxRetryCount = 100
var acceptFD: Int32 = -1
repeat {
var acceptAddr = sockaddr_in()
var addrSize = socklen_t(MemoryLayout<sockaddr_in>.size)
acceptFD = withUnsafeMutablePointer(to: &acceptAddr) { pointer in
return accept(self.socketfd, UnsafeMutableRawPointer(pointer).assumingMemoryBound(to: sockaddr.self), &addrSize)
}
@@ -159,17 +243,22 @@ internal class PoCSocket {
}
}
while acceptFD < 0 && maxRetryCount > 0
if acceptFD < 0 {
throw PoCSocketError.SocketOSError(errno: errno)
}
retVal.isConnected = true
retVal.socketfd = acceptFD
// TLS delegate does post accept handling and verification
if let tls = self.TLSdelegate {
try tls.didAccept(connection: retVal)
}
return retVal
}
/// call `bind(2)` and `listen(2)`
///
/// - Parameters:
@@ -182,17 +271,22 @@ internal class PoCSocket {
#else
socketfd = socket(Int32(AF_INET), Int32(SOCK_STREAM), Int32(IPPROTO_TCP))
#endif
if socketfd <= 0 {
throw PoCSocketError.InvalidSocketError
}
}
// Initialize delegate
if let tls = self.TLSdelegate {
try tls.didCreateServer()
}
var on: Int32 = 1
// Allow address reuse
if setsockopt(self.socketfd, SOL_SOCKET, SO_REUSEADDR, &on, socklen_t(MemoryLayout<Int32>.size)) < 0 {
throw PoCSocketError.SocketOSError(errno: errno)
}
// Allow port reuse
if setsockopt(self.socketfd, SOL_SOCKET, SO_REUSEPORT, &on, socklen_t(MemoryLayout<Int32>.size)) < 0 {
throw PoCSocketError.SocketOSError(errno: errno)
@@ -203,27 +297,23 @@ internal class PoCSocket {
sin_family: sa_family_t(AF_INET),
sin_port: htons(UInt16(port)),
sin_addr: in_addr(s_addr: in_addr_t(0)),
sin_zero:(0, 0, 0, 0, 0, 0, 0, 0))
sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
#else
var addr = sockaddr_in(
sin_len: UInt8(MemoryLayout<sockaddr_in>.stride),
sin_family: UInt8(AF_INET),
sin_port: (Int(OSHostByteOrder()) != OSLittleEndian ? UInt16(port) : _OSSwapInt16(UInt16(port))),
sin_addr: in_addr(s_addr: in_addr_t(0)),
sin_zero:(0, 0, 0, 0, 0, 0, 0, 0))
sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
#endif
let _ = withUnsafePointer(to: &addr) {
_ = withUnsafePointer(to: &addr) {
bind(self.socketfd, UnsafePointer<sockaddr>(OpaquePointer($0)), socklen_t(MemoryLayout<sockaddr_in>.size))
}
//print("bindResult is \(bindResult)")
let _ = listen(self.socketfd, maxBacklogSize)
_ = listen(self.socketfd, maxBacklogSize)
isListening = true
//print("listenResult is \(listenResult)")
var addr_in = sockaddr_in()
@@ -238,17 +328,15 @@ internal class PoCSocket {
return Int32(Int(OSHostByteOrder()) != OSLittleEndian ? addr_in.sin_port.littleEndian : addr_in.sin_port.bigEndian)
#endif
}
//print("listeningPort is \(listeningPort)")
}
/// Check to see if socket is being used
///
/// - Returns: whether socket is listening or connected
internal func isOpen() -> Bool {
return isListening || isConnected
}
/// Sets the socket to Blocking or non-blocking mode.
///
/// - Parameter mode: true for blocking, false for nonBlocking
@@ -260,9 +348,9 @@ internal class PoCSocket {
//Failed
throw PoCSocketError.SocketOSError(errno: errno)
}
let newFlags = mode ? flags & ~O_NONBLOCK : flags | O_NONBLOCK
let result = fcntl(self.socketfd, F_SETFL, newFlags)
if result < 0 {
//Failed
@@ -8,28 +8,47 @@
import Foundation
import Dispatch
import ServerSecurity
///:nodoc:
public class PoCSocketConnectionListener: ParserConnecting {
///socket(2) wrapper object
/// socket(2) wrapper object
var socket: PoCSocket?
///ivar for the thing that manages the CHTTP Parser
/// ivar for the thing that manages the CHTTP Parser
var parser: StreamingParser?
///Save the socket file descriptor so we can loook at it for debugging purposes
/// Save the socket file descriptor so we can loook at it for debugging purposes
var socketFD: Int32
var shouldShutdown: Bool = false
///Flag to track whether we've been told to shutdown or not (with lock)
private let _shouldShutdownLock = DispatchSemaphore(value: 1)
private var _shouldShutdown: Bool = false
var shouldShutdown: Bool {
get {
_shouldShutdownLock.wait()
defer {
_shouldShutdownLock.signal()
}
return _shouldShutdown
}
set {
_shouldShutdownLock.wait()
defer {
_shouldShutdownLock.signal()
}
_shouldShutdown = newValue
}
}
/// Queues for managing access to the socket without blocking the world
let socketReaderQueue: DispatchQueue
let socketWriterQueue: DispatchQueue
///Event handler for reading from the socket
/// Event handler for reading from the socket
private var readerSource: DispatchSourceRead?
///Flag to track whether we're in the middle of a response or not (with lock)
/// Flag to track whether we're in the middle of a response or not (with lock)
private let _responseCompletedLock = DispatchSemaphore(value: 1)
private var _responseCompleted: Bool = false
var responseCompleted: Bool {
@@ -49,7 +68,7 @@ public class PoCSocketConnectionListener: ParserConnecting {
}
}
///Flag to track whether we've received a socket error or not (with lock)
/// Flag to track whether we've received a socket error or not (with lock)
private let _errorOccurredLock = DispatchSemaphore(value: 1)
private var _errorOccurred: Bool = false
var errorOccurred: Bool {
@@ -69,8 +88,28 @@ public class PoCSocketConnectionListener: ParserConnecting {
}
}
///Largest number of bytes we're willing to allocate for a Read
// it's an anti-heartbleed-type paranoia check
///Flag to track whether we've already called cleanup or not (with lock)
private let _cleanupCalledLock = DispatchSemaphore(value: 1)
private var _cleanupCalled: Bool = false
var cleanupCalled: Bool {
get {
_cleanupCalledLock.wait()
defer {
_cleanupCalledLock.signal()
}
return _cleanupCalled
}
set {
_cleanupCalledLock.wait()
defer {
_cleanupCalledLock.signal()
}
_cleanupCalled = newValue
}
}
/// Largest number of bytes we're willing to allocate for a Read
/// it's an anti-heartbleed-type paranoia check
private var maxReadLength: Int = 1048576
/// initializer
@@ -92,16 +131,13 @@ public class PoCSocketConnectionListener: ParserConnecting {
/// Check if socket is still open. Used to decide whether it should be closed/pruned after timeout
public var isOpen: Bool {
guard let socket = self.socket else {
return false
}
return socket.isOpen()
return self.socket?.isOpen() ?? false
}
/// Close the socket and free up memory unless we're in the middle of a request
func close() {
self.shouldShutdown = true
if !self.responseCompleted && !self.errorOccurred {
return
}
@@ -109,7 +145,7 @@ public class PoCSocketConnectionListener: ParserConnecting {
self.socket?.shutdownAndClose()
}
//In a perfect world, we wouldn't have to clean this all up explicitly,
// In a perfect world, we wouldn't have to clean this all up explicitly,
// but KDE/heaptrack informs us we're in far from a perfect world
if !(self.readerSource?.isCancelled ?? true) {
@@ -151,54 +187,79 @@ public class PoCSocketConnectionListener: ParserConnecting {
/// Check if the socket is idle, and if so, call close()
func closeIfIdleSocket() {
if !self.responseCompleted {
// We're in the middle of a connection - we're not idle
return
}
let now = Date().timeIntervalSinceReferenceDate
if let keepAliveUntil = parser?.keepAliveUntil, now >= keepAliveUntil {
print("Closing idle socket \(socketFD)")
close()
}
}
func cleanup() {
self.readerSource?.setEventHandler(handler: nil)
self.readerSource?.setCancelHandler(handler: nil)
guard !cleanupCalled else {
// This prevents a rare crash (~1 in 300,000) where cleanup is called from both reader and writer
// queues simultaneously
return
}
self.readerSource = nil
self.socket = nil
self.parser?.parserConnector = nil //allows for memory to be reclaimed
self.parser = nil
//allow for memory to be reclaimed
if let strongReaderSource = self.readerSource {
strongReaderSource.setEventHandler(handler: nil)
strongReaderSource.setCancelHandler(handler: nil)
}
if let strongParser = self.parser {
strongParser.parserConnector = nil
}
cleanupCalled = true
}
/// Called by the parser to let us know that a response has started being created
public func responseBeginning() {
self.socketWriterQueue.async { [weak self] in
self?.responseCompleted = false
}
self.responseCompleted = false
}
/// Called by the parser to let us know that a response is complete, and we can close after timeout
public func responseComplete() {
self.responseCompleted = true
self.socketWriterQueue.async { [weak self] in
self?.responseCompleted = true
if self?.readerSource?.isCancelled ?? true {
self?.close()
}
}
}
/// Called by the parser to let us know that a response is complete and we should close the socket
public func responseCompleteCloseWriter() {
self.responseCompleted = true
self.socketWriterQueue.async { [weak self] in
self?.responseCompleted = true
self?.close()
}
}
/// Starts reading from the socket and feeding that data to the parser
public func process() {
try! socket?.setBlocking(mode: true)
let tempReaderSource = DispatchSource.makeReadSource(fileDescriptor: socket?.socketfd ?? -1,
queue: socketReaderQueue)
let tempReaderSource: DispatchSourceRead
// Make sure we have a socket here. Don't use guard so that
// we don't encourage strongSocket to be used in the
// event handler, which could cause a leak
if let strongSocket = socket {
do {
try strongSocket.setBlocking(mode: true)
tempReaderSource = DispatchSource.makeReadSource(fileDescriptor: strongSocket.socketfd,
queue: socketReaderQueue)
self.readerSource = tempReaderSource
} catch {
print("Socket cannot be set to Blocking in process(): \(error)")
return
}
} else {
print("Socket is nil in process()")
return
}
tempReaderSource.setEventHandler { [weak self] in
guard let strongSelf = self else {
@@ -214,52 +275,72 @@ public class PoCSocketConnectionListener: ParserConnecting {
strongSelf.cleanup()
return
}
var length = 1 //initial value
do {
if strongSelf.socket?.socketfd ?? -1 > 0 {
var maxLength: Int = Int(strongSelf.readerSource?.data ?? 0)
if (maxLength > strongSelf.maxReadLength) || (maxLength <= 0) {
if let strongSocket = strongSelf.socket {
var length = 1 //initial value
do {
if strongSocket.socketfd > 0 {
var maxLength: Int = Int(strongSelf.readerSource?.data ?? 0)
if (maxLength > strongSelf.maxReadLength) || (maxLength <= 0) {
maxLength = strongSelf.maxReadLength
}
var readBuffer: UnsafeMutablePointer<Int8> = UnsafeMutablePointer<Int8>.allocate(capacity: maxLength)
length = try strongSelf.socket?.socketRead(into: &readBuffer, maxLength:maxLength) ?? -1
if length > 0 {
self?.responseCompleted = false
let data = Data(bytes: readBuffer, count: length)
let numberParsed = strongSelf.parser?.readStream(data:data) ?? 0
if numberParsed != data.count {
print("Error: wrong number of bytes consumed by parser (\(numberParsed) instead of \(data.count)")
}
}
} else {
print("bad socket FD while reading")
length = -1
// make sure we read all data buffered by TLS layer
if (strongSocket.TLSdelegate != nil && (maxLength < TLSConstants.maxTLSRecordLength)) {
maxLength = TLSConstants.maxTLSRecordLength
}
var readBuffer: UnsafeMutablePointer<Int8> = UnsafeMutablePointer<Int8>.allocate(capacity: maxLength)
length = try strongSocket.socketRead(into: &readBuffer, maxLength:maxLength)
if length > 0 {
strongSelf.responseCompleted = false
let data = Data(bytes: readBuffer, count: length)
let numberParsed = strongSelf.parser?.readStream(data:data) ?? 0
defer {
readBuffer.deallocate(capacity: maxLength)
}
} catch {
print("ReaderSource Event Error: \(error)")
self?.readerSource?.cancel()
self?.errorOccurred = true
self?.close()
}
if length == 0 {
self?.readerSource?.cancel()
}
if length < 0 {
self?.errorOccurred = true
self?.readerSource?.cancel()
self?.close()
if numberParsed != data.count {
print("Error: wrong number of bytes consumed by parser (\(numberParsed) instead of \(data.count)")
}
}
} else {
print("bad socket FD while reading")
length = -1
}
} catch {
//print("ReaderSource Event Error: \(error)")
strongSelf.readerSource?.cancel()
strongSelf.errorOccurred = true
strongSelf.close()
}
if length == 0 {
//print("ReaderSource Read count zero. Cancelling.")
strongSelf.readerSource?.cancel()
}
if length < 0 {
//print("ReaderSource Read count negative. Closing.")
strongSelf.errorOccurred = true
strongSelf.readerSource?.cancel()
strongSelf.close()
}
} else {
//print("ReaderSource Read found nil socket. Closing.")
strongSelf.errorOccurred = true
strongSelf.readerSource?.cancel()
strongSelf.close()
}
}
tempReaderSource.setCancelHandler { [weak self] in
self?.close() //close if we can
if let strongSelf = self {
strongSelf.close() //close if we can
}
}
self.readerSource = tempReaderSource
self.readerSource?.resume()
}
@@ -283,13 +364,18 @@ public class PoCSocketConnectionListener: ParserConnecting {
while written < data.count && !errorOccurred {
try data.withUnsafeBytes { (ptr: UnsafePointer<UInt8>) in
let result = try socket?.socketWrite(from: ptr + offset, bufSize:
data.count - offset) ?? -1
if result < 0 {
print("Received broken write socket indication")
errorOccurred = true
if let strongSocket = socket {
let result = try strongSocket.socketWrite(from: ptr + offset, bufSize:
data.count - offset)
if result < 0 {
//print("Received broken write socket indication")
errorOccurred = true
} else {
written += result
}
} else {
written += result
//print("Socket unexpectedly nil during write")
errorOccurred = true
}
}
offset = data.count - written
@@ -8,7 +8,8 @@
import Dispatch
import Foundation
import ServerSecurity
import TLSService
// MARK: Server
@@ -21,7 +22,7 @@ public class PoCSocketSimpleServer: CurrentConnectionCounting {
/// Collection of listeners of sockets. Used to kill connections on timeout or shutdown
private var connectionListenerList = ConnectionListenerCollection()
// Timer that cleans up idle sockets on expire
/// Timer that cleans up idle sockets on expire
private let pruneSocketTimer: DispatchSourceTimer = DispatchSource.makeTimerSource(queue: DispatchQueue(label: "pruneSocketTimer"))
/// The port we're listening on. Used primarily to query a randomly assigned port during XCTests
@@ -35,7 +36,7 @@ public class PoCSocketSimpleServer: CurrentConnectionCounting {
/// Tuning parameter to set the number of sockets we can accept at one time
private var acceptMax: Int = 8 //sensible default
///Used to stop `accept(2)`ing while shutdown in progress to avoid spurious logs
/// Used to stop `accept(2)`ing while shutdown in progress to avoid spurious logs
private let _isShuttingDownLock = DispatchSemaphore(value: 1)
private var _isShuttingDown: Bool = false
var isShuttingDown: Bool {
@@ -66,10 +67,11 @@ public class PoCSocketSimpleServer: CurrentConnectionCounting {
acceptCount: Int = 0,
maxReadLength: Int = 1048576,
keepAliveTimeout: Double = 5.0,
tls: TLSConfiguration? = nil,
handler: @escaping HTTPRequestHandler) throws {
// Don't let a signal generated by a broken socket kill the server
signal(SIGPIPE, SIG_IGN);
signal(SIGPIPE, SIG_IGN)
if queueCount > 0 {
queueMax = queueCount
@@ -77,6 +79,11 @@ public class PoCSocketSimpleServer: CurrentConnectionCounting {
if acceptCount > 0 {
acceptMax = acceptCount
}
// Set up TLS
if let tlsConfig = tls {
let tlsService = try TLSService(usingConfiguration: tlsConfig)
self.serverSocket.TLSdelegate = tlsService
}
try self.serverSocket.bindAndListen(on: port)
pruneSocketTimer.setEventHandler { [weak self] in
@@ -118,7 +125,7 @@ public class PoCSocketSimpleServer: CurrentConnectionCounting {
let streamingParser = StreamingParser(handler: handler, connectionCounter: self, keepAliveTimeout: keepAliveTimeout)
let readQueue = readQueues[listenerCount % self.queueMax]
let writeQueue = writeQueues[listenerCount % self.queueMax]
let listener = PoCSocketConnectionListener(socket: clientSocket, parser: streamingParser, readQueue:readQueue, writeQueue: writeQueue, maxReadLength: maxReadLength)
let listener = PoCSocketConnectionListener(socket: clientSocket, parser: streamingParser, readQueue: readQueue, writeQueue: writeQueue, maxReadLength: maxReadLength)
listenerCount += 1
acceptSemaphore.wait()
acceptQueue.async { [weak listener] in
@@ -156,10 +163,11 @@ class ConnectionListenerCollection {
}
}
let lock = DispatchSemaphore(value: 1)
/// Lock around access to storage
private let lock = DispatchSemaphore(value: 1)
/// Storage for weak connection listeners
var storage = [WeakConnectionListener<PoCSocketConnectionListener>]()
private var storage = [WeakConnectionListener<PoCSocketConnectionListener>]()
/// Add a new connection to the collection
///
@@ -173,7 +181,7 @@ class ConnectionListenerCollection {
/// Used when shutting down the server to close all connections
func closeAll() {
lock.wait()
storage.filter { nil != $0.value }.forEach { $0.value?.close() }
storage.filter { $0.value != nil }.forEach { $0.value?.close() }
lock.signal()
}
@@ -181,16 +189,15 @@ class ConnectionListenerCollection {
func prune() {
lock.wait()
storage.filter { nil != $0.value }.forEach { $0.value?.closeIfIdleSocket() }
storage = storage.filter { nil != $0.value }.filter { $0.value?.isOpen ?? false }
storage = storage.filter { $0.value != nil }.filter { $0.value?.isOpen ?? false }
lock.signal()
}
/// Count of collections
var count: Int {
lock.wait()
let count = storage.filter { nil != $0.value }.count
let count = storage.filter { $0.value != nil }.count
lock.signal()
return count
}
}
@@ -11,10 +11,10 @@ import HTTP
/// Simple `HTTPRequestHandler` that prints "Hello, World" as per K&R
class AbortAndSendHelloHandler: HTTPRequestHandling {
var chunkCalledCount=0
var chunkLength=0
var chunkCalledCount = 0
var chunkLength = 0
func handle(request: HTTPRequest, response: HTTPResponseWriter ) -> HTTPBodyProcessing {
//Assume the router gave us the right request - at least for now
response.writeHeader(status: .ok, headers: [.transferEncoding: "chunked", "X-foo": "bar"])
@@ -17,7 +17,7 @@ class TestResponseResolver: HTTPResponseWriter {
var response: (status: HTTPResponseStatus, headers: HTTPHeaders)?
var responseBody: HTTPResponseBody?
///Flag to track whether our handler has told us not to call it anymore
private let _shouldStopProcessingBodyLock = DispatchSemaphore(value: 1)
private var _shouldStopProcessingBody: Bool = false
@@ -57,7 +57,7 @@ class TestResponseResolver: HTTPResponseWriter {
switch chunkHandler {
case .processBody(let handler):
_shouldStopProcessingBodyLock.wait()
handler(.chunk(data: self.requestBody, finishedProcessing: {self._shouldStopProcessingBodyLock.signal()}), &_shouldStopProcessingBody)
handler(.chunk(data: self.requestBody, finishedProcessing: { self._shouldStopProcessingBodyLock.signal() }), &_shouldStopProcessingBody)
var dummy = false
handler(.end, &dummy)
case .discardBody:
+1 -473
View File
@@ -22,7 +22,7 @@ class ServerTests: XCTestCase {
}
func testEcho() {
let testString="This is a test"
let testString = "This is a test"
let request = HTTPRequest(method: .post, target: "/echo", httpVersion: HTTPVersion(major: 1, minor: 1), headers: ["X-foo": "bar"])
let resolver = TestResponseResolver(request: request, requestBody: testString.data(using: .utf8)!)
resolver.resolveHandler(EchoHandler().handle)
@@ -59,482 +59,10 @@ class ServerTests: XCTestCase {
XCTAssertEqual("Hello, World!", resolver.responseBody?.withUnsafeBytes { String(bytes: $0, encoding: .utf8) } ?? "Nil")
}
func testOkEndToEnd() {
let receivedExpectation = self.expectation(description: "Received web response \(#function)")
let server = HTTPServer()
do {
try server.start(port: 0, handler: OkHandler().handle)
let session = URLSession(configuration: URLSessionConfiguration.default)
let url = URL(string: "http://localhost:\(server.port)/")!
print("Test \(#function) on port \(server.port)")
let dataTask = session.dataTask(with: url) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
receivedExpectation.fulfill()
}
dataTask.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
func testHelloEndToEnd() {
let receivedExpectation = self.expectation(description: "Received web response \(#function)")
let server = HTTPServer()
do {
try server.start(port: 0, handler: HelloWorldHandler().handle)
let session = URLSession(configuration: URLSessionConfiguration.default)
let url = URL(string: "http://localhost:\(server.port)/helloworld")!
print("Test \(#function) on port \(server.port)")
let dataTask = session.dataTask(with: url) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual("Hello, World!", String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil")
receivedExpectation.fulfill()
}
dataTask.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
func testSimpleHelloEndToEnd() {
let receivedExpectation = self.expectation(description: "Received web response \(#function)")
let simpleHelloWebApp = SimpleResponseCreator { (_, body) -> SimpleResponseCreator.Response in
return SimpleResponseCreator.Response(
status: .ok,
headers: ["X-foo": "bar"],
body: "Hello, World!".data(using: .utf8)!
)
}
let server = HTTPServer()
do {
try server.start(port: 0, handler: simpleHelloWebApp.handle)
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
let session = URLSession(configuration: URLSessionConfiguration.default)
let url = URL(string: "http://localhost:\(server.port)/helloworld")!
print("Test \(#function) on port \(server.port)")
let dataTask = session.dataTask(with: url) { (responseBody, rawResponse, error) in
print("\(#function) dataTask returned")
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
let responseString = String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil"
XCTAssertEqual("Hello, World!", responseString)
print("\(#function) fulfilling expectation")
receivedExpectation.fulfill()
}
dataTask.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
print("\(#function) stopping server")
}
func testRequestEchoEndToEnd() {
let receivedExpectation = self.expectation(description: "Received web response \(#function)")
let testString="This is a test"
let server = HTTPServer()
do {
try server.start(port: 0, handler: EchoHandler().handle)
let session = URLSession(configuration: URLSessionConfiguration.default)
let url = URL(string: "http://localhost:\(server.port)/echo")!
print("Test \(#function) on port \(server.port)")
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = testString.data(using: .utf8)
request.setValue("text/plain", forHTTPHeaderField: "Content-Type")
let dataTask = session.dataTask(with: request) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual(testString, String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil")
receivedExpectation.fulfill()
}
dataTask.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
func testRequestKeepAliveEchoEndToEnd() {
let receivedExpectation1 = self.expectation(description: "Received web response 1: \(#function)")
let receivedExpectation2 = self.expectation(description: "Received web response 2: \(#function)")
let receivedExpectation3 = self.expectation(description: "Received web response 3: \(#function)")
let testString1="This is a test"
let testString2="This is a test, too"
let testString3="This is also a test"
let server = HTTPServer()
do {
try server.start(port: 0, handler: EchoHandler().handle)
let session = URLSession(configuration: URLSessionConfiguration.default)
let url = URL(string: "http://localhost:\(server.port)/echo")!
print("Test \(#function) on port \(server.port)")
var request1 = URLRequest(url: url)
request1.httpMethod = "POST"
request1.httpBody = testString1.data(using: .utf8)
request1.setValue("text/plain", forHTTPHeaderField: "Content-Type")
let dataTask1 = session.dataTask(with: request1) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
let headers = response?.allHeaderFields ?? ["": ""]
let connectionHeader: String = headers["Connection"] as? String ?? ""
XCTAssertEqual(connectionHeader, "Keep-Alive", "No Keep-Alive Connection")
XCTAssertNotNil(responseBody, "No Response Body")
XCTAssertEqual(server.connectionCount, 1)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual(testString1, String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil")
var request2 = URLRequest(url: url)
request2.httpMethod = "POST"
request2.httpBody = testString2.data(using: .utf8)
request2.setValue("text/plain", forHTTPHeaderField: "Content-Type")
let dataTask2 = session.dataTask(with: request2) { (responseBody2, rawResponse2, error2) in
let response2 = rawResponse2 as? HTTPURLResponse
XCTAssertNil(error2, "\(error2!.localizedDescription)")
XCTAssertNotNil(response2)
let headers = response2?.allHeaderFields ?? ["": ""]
let connectionHeader: String = headers["Connection"] as? String ?? ""
XCTAssertEqual(connectionHeader, "Keep-Alive", "No Keep-Alive Connection")
XCTAssertEqual(server.connectionCount, 1)
XCTAssertNotNil(responseBody2)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response2?.statusCode ?? 0)
XCTAssertEqual(testString2, String(data: responseBody2 ?? Data(), encoding: .utf8) ?? "Nil")
var request3 = URLRequest(url: url)
request3.httpMethod = "POST"
request3.httpBody = testString3.data(using: .utf8)
request3.setValue("text/plain", forHTTPHeaderField: "Content-Type")
let dataTask3 = session.dataTask(with: request3) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
let headers = response?.allHeaderFields ?? ["": ""]
let connectionHeader: String = headers["Connection"] as? String ?? ""
XCTAssertEqual(connectionHeader, "Keep-Alive", "No Keep-Alive Connection")
XCTAssertEqual(server.connectionCount, 1)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual(testString3, String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil")
receivedExpectation3.fulfill()
}
dataTask3.resume()
receivedExpectation2.fulfill()
}
dataTask2.resume()
receivedExpectation1.fulfill()
}
dataTask1.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
//server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
func testMultipleRequestWithoutKeepAliveEchoEndToEnd() {
let receivedExpectation1 = self.expectation(description: "Received web response 1: \(#function)")
let receivedExpectation2 = self.expectation(description: "Received web response 2: \(#function)")
let receivedExpectation3 = self.expectation(description: "Received web response 3: \(#function)")
let testString1="This is a test"
let testString2="This is a test, too"
let testString3="This is also a test"
let server = HTTPServer()
do {
try server.start(port: 0, handler: EchoHandler().handle)
let session = URLSession(configuration: URLSessionConfiguration.default)
let url1 = URL(string: "http://localhost:\(server.port)/echo")!
print("Test \(#function) on port \(server.port)")
var request1 = URLRequest(url: url1)
request1.httpMethod = "POST"
request1.httpBody = testString1.data(using: .utf8)
request1.setValue("text/plain", forHTTPHeaderField: "Content-Type")
let dataTask1 = session.dataTask(with: request1) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
let headers = response?.allHeaderFields ?? ["": ""]
let connectionHeader: String = headers["Connection"] as? String ?? ""
let keepAliveHeader = headers["Connection"]
XCTAssertEqual(connectionHeader, "Keep-Alive", "No Keep-Alive Connection")
XCTAssertNotNil(keepAliveHeader)
XCTAssertNotNil(responseBody, "No Keep-Alive Header")
XCTAssertEqual(server.connectionCount, 1)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual(testString1, String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil")
let url2 = URL(string: "http://127.0.0.1:\(server.port)/echo")!
var request2 = URLRequest(url: url2)
request2.httpMethod = "POST"
request2.httpBody = testString2.data(using: .utf8)
request2.setValue("text/plain", forHTTPHeaderField: "Content-Type")
request2.setValue("close", forHTTPHeaderField: "Connection")
let dataTask2 = session.dataTask(with: request2) { (responseBody2, rawResponse2, error2) in
let response2 = rawResponse2 as? HTTPURLResponse
XCTAssertNil(error2, "\(error2!.localizedDescription)")
XCTAssertNotNil(response2)
let headers = response2?.allHeaderFields ?? ["": ""]
let connectionHeader: String = headers["Connection"] as? String ?? ""
let keepAliveHeader = headers["Connection"]
XCTAssertEqual(connectionHeader, "Keep-Alive", "No Keep-Alive Connection")
XCTAssertNotNil(keepAliveHeader, "No Keep-Alive Header")
XCTAssertEqual(server.connectionCount, 2)
XCTAssertNotNil(responseBody2)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response2?.statusCode ?? 0)
XCTAssertEqual(testString2, String(data: responseBody2 ?? Data(), encoding: .utf8) ?? "Nil")
let url3 = URL(string: "http://0.0.0.0:\(server.port)/echo")!
var request3 = URLRequest(url: url3)
request3.httpMethod = "POST"
request3.httpBody = testString3.data(using: .utf8)
request3.setValue("text/plain", forHTTPHeaderField: "Content-Type")
request3.setValue("close", forHTTPHeaderField: "Connection")
let dataTask3 = session.dataTask(with: request3) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
let headers = response?.allHeaderFields ?? ["": ""]
let connectionHeader: String = headers["Connection"] as? String ?? ""
let keepAliveHeader = headers["Connection"]
XCTAssertEqual(connectionHeader, "Keep-Alive", "No Keep-Alive Connection")
XCTAssertNotNil(keepAliveHeader, "No Keep-Alive Header")
XCTAssertEqual(server.connectionCount, 3)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual(testString3, String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil")
receivedExpectation3.fulfill()
}
dataTask3.resume()
receivedExpectation2.fulfill()
}
dataTask2.resume()
receivedExpectation1.fulfill()
}
dataTask1.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
//server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
func testRequestLargeEchoEndToEnd() {
let receivedExpectation = self.expectation(description: "Received web response \(#function)")
//Use a small chunk size to make sure that we're testing multiple HTTPBodyHandler calls
let chunkSize = 1024
// Get a file we know exists
let executableURL = URL(fileURLWithPath: CommandLine.arguments[0])
let testExecutableData: Data
do {
testExecutableData = try Data(contentsOf: executableURL)
} catch {
XCTFail("Could not create Data from contents of \(executableURL)")
return
}
var testDataLong = testExecutableData + testExecutableData + testExecutableData + testExecutableData
let length = testDataLong.count
let keep = 16385
let remove = length - keep
if remove > 0 {
testDataLong.removeLast(remove)
}
let testData = Data(testDataLong)
let server = PoCSocketSimpleServer()
do {
try server.start(port: 0, maxReadLength: chunkSize, handler: EchoHandler().handle)
let session = URLSession(configuration: URLSessionConfiguration.default)
let url = URL(string: "http://localhost:\(server.port)/echo")!
print("Test \(#function) on port \(server.port)")
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = testData
let dataTask = session.dataTask(with: request) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual(testData, responseBody ?? Data())
receivedExpectation.fulfill()
}
dataTask.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
func testRequestLargePostHelloWorld() {
let receivedExpectation = self.expectation(description: "Received web response \(#function)")
//Use a small chunk size to make sure that we stop after one HTTPBodyHandler call
let chunkSize = 1024
// Get a file we know exists
let executableURL = URL(fileURLWithPath: CommandLine.arguments[0])
let testExecutableData: Data
do {
testExecutableData = try Data(contentsOf: executableURL)
} catch {
XCTFail("Could not create Data from contents of \(executableURL)")
return
}
//Make sure there's data there
XCTAssertNotNil(testExecutableData)
let executableLength = testExecutableData.count
let server = PoCSocketSimpleServer()
do {
let testHandler = AbortAndSendHelloHandler()
try server.start(port: 0, maxReadLength: chunkSize, handler: testHandler.handle)
let session = URLSession(configuration: URLSessionConfiguration.default)
let url = URL(string: "http://localhost:\(server.port)/echo")!
print("Test \(#function) on port \(server.port)")
var request = URLRequest(url: url)
request.httpMethod = "POST"
let uploadTask = session.uploadTask(with: request, fromFile: executableURL) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual("Hello, World!", String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil")
XCTAssertEqual(Int(testHandler.chunkCalledCount), 1)
XCTAssertLessThan(testHandler.chunkLength, executableLength, "Should have written less than the length of the file")
XCTAssertLessThanOrEqual(Int(testHandler.chunkLength), chunkSize)
receivedExpectation.fulfill()
}
uploadTask.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
func testExplicitCloseConnections() {
let expectation = self.expectation(description: "0 Open Connection")
let server = PoCSocketSimpleServer()
let keepAliveTimeout = 0.1
do {
try server.start(port: 0, keepAliveTimeout: keepAliveTimeout, handler: OkHandler().handle)
let session = URLSession(configuration: URLSessionConfiguration.default)
let url1 = URL(string: "http://localhost:\(server.port)")!
var request = URLRequest(url: url1)
request.httpMethod = "POST"
request.setValue("close", forHTTPHeaderField: "Connection")
let dataTask1 = session.dataTask(with: request) { (responseBody, rawResponse, error) in
XCTAssertNil(error, "\(error!.localizedDescription)")
#if os(Linux)
XCTAssertEqual(server.connectionCount, 0)
expectation.fulfill()
// Darwin's URLSession replaces the `Connection: close` header with `Connection: keep-alive`, so allow it to expire
#else
DispatchQueue.main.asyncAfter(deadline: .now() + keepAliveTimeout) {
XCTAssertEqual(server.connectionCount, 0)
expectation.fulfill()
}
#endif
}
dataTask1.resume()
self.waitForExpectations(timeout: 30) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
static var allTests = [
("testEcho", testEcho),
("testHello", testHello),
("testSimpleHello", testSimpleHello),
("testResponseOK", testResponseOK),
("testOkEndToEnd", testOkEndToEnd),
("testHelloEndToEnd", testHelloEndToEnd),
("testSimpleHelloEndToEnd", testSimpleHelloEndToEnd),
("testRequestEchoEndToEnd", testRequestEchoEndToEnd),
("testRequestKeepAliveEchoEndToEnd", testRequestKeepAliveEchoEndToEnd),
("testRequestLargeEchoEndToEnd", testRequestLargeEchoEndToEnd),
("testExplicitCloseConnections", testExplicitCloseConnections),
("testRequestLargePostHelloWorld", testRequestLargePostHelloWorld),
]
}
+494
View File
@@ -0,0 +1,494 @@
// This source file is part of the Swift.org Server APIs open source project
//
// Copyright (c) 2017 Swift Server API project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
//
import XCTest
import Dispatch
@testable import HTTP
class ServerTestsEndToEnd: XCTestCase {
func testOkEndToEnd() {
let receivedExpectation = self.expectation(description: "Received web response \(#function)")
let options = HTTPServer.Options(onPort: 0)
let server = HTTPServer(with: options, requestHandler: OkHandler().handle)
do {
try server.start()
let session = URLSession(configuration: .default)
let url = URL(string: "http://localhost:\(server.port)/")!
print("Test \(#function) on port \(server.port)")
let dataTask = session.dataTask(with: url) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
receivedExpectation.fulfill()
}
dataTask.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
func testHelloEndToEnd() {
let receivedExpectation = self.expectation(description: "Received web response \(#function)")
let options = HTTPServer.Options(onPort: 0)
let server = HTTPServer(with: options, requestHandler: HelloWorldHandler().handle)
do {
try server.start()
let session = URLSession(configuration: .default)
let url = URL(string: "http://localhost:\(server.port)/helloworld")!
print("Test \(#function) on port \(server.port)")
let dataTask = session.dataTask(with: url) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual("Hello, World!", String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil")
receivedExpectation.fulfill()
}
dataTask.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
func testSimpleHelloEndToEnd() {
let receivedExpectation = self.expectation(description: "Received web response \(#function)")
let simpleHelloWebApp = SimpleResponseCreator { (_, body) -> SimpleResponseCreator.Response in
return SimpleResponseCreator.Response(
status: .ok,
headers: ["X-foo": "bar"],
body: "Hello, World!".data(using: .utf8)!
)
}
let options = HTTPServer.Options(onPort: 0)
let server = HTTPServer(with: options, requestHandler: simpleHelloWebApp.handle)
do {
try server.start()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
let session = URLSession(configuration: .default)
let url = URL(string: "http://localhost:\(server.port)/helloworld")!
print("Test \(#function) on port \(server.port)")
let dataTask = session.dataTask(with: url) { (responseBody, rawResponse, error) in
print("\(#function) dataTask returned")
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
let responseString = String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil"
XCTAssertEqual("Hello, World!", responseString)
print("\(#function) fulfilling expectation")
receivedExpectation.fulfill()
}
dataTask.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
print("\(#function) stopping server")
}
func testRequestEchoEndToEnd() {
let receivedExpectation = self.expectation(description: "Received web response \(#function)")
let testString="This is a test"
let options = HTTPServer.Options(onPort: 0)
let server = HTTPServer(with: options, requestHandler: EchoHandler().handle)
do {
try server.start()
let session = URLSession(configuration: .default)
let url = URL(string: "http://localhost:\(server.port)/echo")!
print("Test \(#function) on port \(server.port)")
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = testString.data(using: .utf8)
request.setValue("text/plain", forHTTPHeaderField: "Content-Type")
let dataTask = session.dataTask(with: request) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual(testString, String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil")
receivedExpectation.fulfill()
}
dataTask.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
func testRequestKeepAliveEchoEndToEnd() {
let receivedExpectation1 = self.expectation(description: "Received web response 1: \(#function)")
let receivedExpectation2 = self.expectation(description: "Received web response 2: \(#function)")
let receivedExpectation3 = self.expectation(description: "Received web response 3: \(#function)")
let testString1="This is a test"
let testString2="This is a test, too"
let testString3="This is also a test"
let options = HTTPServer.Options(onPort: 0)
let server = HTTPServer(with: options, requestHandler: EchoHandler().handle)
do {
try server.start()
let session = URLSession(configuration: .default)
let url = URL(string: "http://localhost:\(server.port)/echo")!
print("Test \(#function) on port \(server.port)")
var request1 = URLRequest(url: url)
request1.httpMethod = "POST"
request1.httpBody = testString1.data(using: .utf8)
request1.setValue("text/plain", forHTTPHeaderField: "Content-Type")
let dataTask1 = session.dataTask(with: request1) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
let headers = response?.allHeaderFields ?? ["": ""]
let connectionHeader: String = headers["Connection"] as? String ?? ""
XCTAssertEqual(connectionHeader, "Keep-Alive", "No Keep-Alive Connection")
XCTAssertNotNil(responseBody, "No Response Body")
XCTAssertEqual(server.connectionCount, 1)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual(testString1, String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil")
var request2 = URLRequest(url: url)
request2.httpMethod = "POST"
request2.httpBody = testString2.data(using: .utf8)
request2.setValue("text/plain", forHTTPHeaderField: "Content-Type")
let dataTask2 = session.dataTask(with: request2) { (responseBody2, rawResponse2, error2) in
let response2 = rawResponse2 as? HTTPURLResponse
XCTAssertNil(error2, "\(error2!.localizedDescription)")
XCTAssertNotNil(response2)
let headers = response2?.allHeaderFields ?? ["": ""]
let connectionHeader: String = headers["Connection"] as? String ?? ""
XCTAssertEqual(connectionHeader, "Keep-Alive", "No Keep-Alive Connection")
XCTAssertEqual(server.connectionCount, 1)
XCTAssertNotNil(responseBody2)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response2?.statusCode ?? 0)
XCTAssertEqual(testString2, String(data: responseBody2 ?? Data(), encoding: .utf8) ?? "Nil")
var request3 = URLRequest(url: url)
request3.httpMethod = "POST"
request3.httpBody = testString3.data(using: .utf8)
request3.setValue("text/plain", forHTTPHeaderField: "Content-Type")
let dataTask3 = session.dataTask(with: request3) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
let headers = response?.allHeaderFields ?? ["": ""]
let connectionHeader: String = headers["Connection"] as? String ?? ""
XCTAssertEqual(connectionHeader, "Keep-Alive", "No Keep-Alive Connection")
XCTAssertEqual(server.connectionCount, 1)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual(testString3, String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil")
receivedExpectation3.fulfill()
}
dataTask3.resume()
receivedExpectation2.fulfill()
}
dataTask2.resume()
receivedExpectation1.fulfill()
}
dataTask1.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
func testMultipleRequestWithoutKeepAliveEchoEndToEnd() {
let receivedExpectation1 = self.expectation(description: "Received web response 1: \(#function)")
let receivedExpectation2 = self.expectation(description: "Received web response 2: \(#function)")
let receivedExpectation3 = self.expectation(description: "Received web response 3: \(#function)")
let testString1="This is a test"
let testString2="This is a test, too"
let testString3="This is also a test"
let options = HTTPServer.Options(onPort: 0)
let server = HTTPServer(with: options, requestHandler: EchoHandler().handle)
do {
try server.start()
let session = URLSession(configuration: .default)
let url1 = URL(string: "http://localhost:\(server.port)/echo")!
print("Test \(#function) on port \(server.port)")
var request1 = URLRequest(url: url1)
request1.httpMethod = "POST"
request1.httpBody = testString1.data(using: .utf8)
request1.setValue("text/plain", forHTTPHeaderField: "Content-Type")
let dataTask1 = session.dataTask(with: request1) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
let headers = response?.allHeaderFields ?? ["": ""]
let connectionHeader: String = headers["Connection"] as? String ?? ""
let keepAliveHeader = headers["Connection"]
XCTAssertEqual(connectionHeader, "Keep-Alive", "No Keep-Alive Connection")
XCTAssertNotNil(keepAliveHeader)
XCTAssertNotNil(responseBody, "No Keep-Alive Header")
XCTAssertEqual(server.connectionCount, 1)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual(testString1, String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil")
let url2 = URL(string: "http://127.0.0.1:\(server.port)/echo")!
var request2 = URLRequest(url: url2)
request2.httpMethod = "POST"
request2.httpBody = testString2.data(using: .utf8)
request2.setValue("text/plain", forHTTPHeaderField: "Content-Type")
request2.setValue("close", forHTTPHeaderField: "Connection")
let dataTask2 = session.dataTask(with: request2) { (responseBody2, rawResponse2, error2) in
let response2 = rawResponse2 as? HTTPURLResponse
XCTAssertNil(error2, "\(error2!.localizedDescription)")
XCTAssertNotNil(response2)
let headers = response2?.allHeaderFields ?? ["": ""]
let connectionHeader: String = headers["Connection"] as? String ?? ""
let keepAliveHeader = headers["Connection"]
XCTAssertEqual(connectionHeader, "Keep-Alive", "No Keep-Alive Connection")
XCTAssertNotNil(keepAliveHeader, "No Keep-Alive Header")
XCTAssertEqual(server.connectionCount, 2)
XCTAssertNotNil(responseBody2)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response2?.statusCode ?? 0)
XCTAssertEqual(testString2, String(data: responseBody2 ?? Data(), encoding: .utf8) ?? "Nil")
let url3 = URL(string: "http://0.0.0.0:\(server.port)/echo")!
var request3 = URLRequest(url: url3)
request3.httpMethod = "POST"
request3.httpBody = testString3.data(using: .utf8)
request3.setValue("text/plain", forHTTPHeaderField: "Content-Type")
request3.setValue("close", forHTTPHeaderField: "Connection")
let dataTask3 = session.dataTask(with: request3) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
let headers = response?.allHeaderFields ?? ["": ""]
let connectionHeader: String = headers["Connection"] as? String ?? ""
let keepAliveHeader = headers["Connection"]
XCTAssertEqual(connectionHeader, "Keep-Alive", "No Keep-Alive Connection")
XCTAssertNotNil(keepAliveHeader, "No Keep-Alive Header")
XCTAssertEqual(server.connectionCount, 3)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual(testString3, String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil")
receivedExpectation3.fulfill()
}
dataTask3.resume()
receivedExpectation2.fulfill()
}
dataTask2.resume()
receivedExpectation1.fulfill()
}
dataTask1.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
func testRequestLargeEchoEndToEnd() {
let receivedExpectation = self.expectation(description: "Received web response \(#function)")
//Use a small chunk size to make sure that we're testing multiple HTTPBodyHandler calls
let chunkSize = 1024
// Get a file we know exists
let executableURL = URL(fileURLWithPath: CommandLine.arguments[0])
let testExecutableData: Data
do {
testExecutableData = try Data(contentsOf: executableURL)
} catch {
XCTFail("Could not create Data from contents of \(executableURL)")
return
}
var testDataLong = testExecutableData + testExecutableData + testExecutableData + testExecutableData
let length = testDataLong.count
let keep = 16385
let remove = length - keep
if remove > 0 {
testDataLong.removeLast(remove)
}
let testData = Data(testDataLong)
let server = PoCSocketSimpleServer()
do {
try server.start(port: 0, maxReadLength: chunkSize, handler: EchoHandler().handle)
let session = URLSession(configuration: .default)
let url = URL(string: "http://localhost:\(server.port)/echo")!
print("Test \(#function) on port \(server.port)")
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = testData
let dataTask = session.dataTask(with: request) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual(testData, responseBody ?? Data())
receivedExpectation.fulfill()
}
dataTask.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
func testRequestLargePostHelloWorld() {
let receivedExpectation = self.expectation(description: "Received web response \(#function)")
//Use a small chunk size to make sure that we stop after one HTTPBodyHandler call
let chunkSize = 1024
// Get a file we know exists
let executableURL = URL(fileURLWithPath: CommandLine.arguments[0])
let testExecutableData: Data
do {
testExecutableData = try Data(contentsOf: executableURL)
} catch {
XCTFail("Could not create Data from contents of \(executableURL)")
return
}
//Make sure there's data there
XCTAssertNotNil(testExecutableData)
let executableLength = testExecutableData.count
let server = PoCSocketSimpleServer()
do {
let testHandler = AbortAndSendHelloHandler()
try server.start(port: 0, maxReadLength: chunkSize, handler: testHandler.handle)
let session = URLSession(configuration: .default)
let url = URL(string: "http://localhost:\(server.port)/echo")!
print("Test \(#function) on port \(server.port)")
var request = URLRequest(url: url)
request.httpMethod = "POST"
let uploadTask = session.uploadTask(with: request, fromFile: executableURL) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual("Hello, World!", String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil")
XCTAssertEqual(Int(testHandler.chunkCalledCount), 1)
XCTAssertLessThan(testHandler.chunkLength, executableLength, "Should have written less than the length of the file")
XCTAssertLessThanOrEqual(Int(testHandler.chunkLength), chunkSize)
receivedExpectation.fulfill()
}
uploadTask.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
func testExplicitCloseConnections() {
let expectation = self.expectation(description: "0 Open Connection")
let server = PoCSocketSimpleServer()
let keepAliveTimeout = 0.1
do {
try server.start(port: 0, keepAliveTimeout: keepAliveTimeout, handler: OkHandler().handle)
let session = URLSession(configuration: .default)
let url1 = URL(string: "http://localhost:\(server.port)")!
var request = URLRequest(url: url1)
request.httpMethod = "POST"
request.setValue("close", forHTTPHeaderField: "Connection")
let dataTask1 = session.dataTask(with: request) { (_, _, error) in
XCTAssertNil(error, "\(error!.localizedDescription)")
#if os(Linux)
XCTAssertEqual(server.connectionCount, 0)
expectation.fulfill()
// Darwin's URLSession replaces the `Connection: close` header with `Connection: keep-alive`, so allow it to expire
#else
DispatchQueue.main.asyncAfter(deadline: .now() + keepAliveTimeout) {
XCTAssertEqual(server.connectionCount, 0)
expectation.fulfill()
}
#endif
}
dataTask1.resume()
self.waitForExpectations(timeout: 30) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
static var allTests = [
("testOkEndToEnd", testOkEndToEnd),
("testHelloEndToEnd", testHelloEndToEnd),
("testSimpleHelloEndToEnd", testSimpleHelloEndToEnd),
("testRequestEchoEndToEnd", testRequestEchoEndToEnd),
("testRequestKeepAliveEchoEndToEnd", testRequestKeepAliveEchoEndToEnd),
("testRequestLargeEchoEndToEnd", testRequestLargeEchoEndToEnd),
("testExplicitCloseConnections", testExplicitCloseConnections),
("testRequestLargePostHelloWorld", testRequestLargePostHelloWorld),
]
}
+572
View File
@@ -0,0 +1,572 @@
// This source file is part of the Swift.org Server APIs open source project
//
// Copyright (c) 2017 Swift Server API project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
//
import XCTest
import Dispatch
import ServerSecurity
@testable import HTTP
class TLSServerTests: XCTestCase {
func testOkEndToEndTLSwithCA() {
let config = createCASignedTLSConfig()
testOkEndToEndInternal(config: config, selfsigned: false)
}
func testOkEndToEndTLSwithSelfSigned() {
let config = createSelfSignedTLSConfig()
testOkEndToEndInternal(config: config, selfsigned: true)
}
func testOkEndToEndInternal(config: TLSConfiguration, selfsigned: Bool) {
let receivedExpectation = self.expectation(description: "Received web response \(#function)")
let urlStr: String
let session: URLSession
let server = HTTPServer(with: HTTPServer.Options(onPort: 0, tlsConf: config), requestHandler: OkHandler().handle)
do {
try server.start()
if selfsigned {
#if os(OSX)
session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue:OperationQueue.main)
#else
// delegate in URLSession in Linux is not implemented. Using this to compile but it will fail if run on Linux.
session = URLSession(configuration: URLSessionConfiguration.default)
#endif
urlStr = "localhost"
} else {
session = URLSession(configuration: URLSessionConfiguration.default)
urlStr = "ssl.gelareh.xyz"
}
let url = URL(string: "https://\(urlStr):\(server.port)/")!
print("Test \(#function) on port \(server.port)")
print("url = \(url.absoluteString) ")
let dataTask = session.dataTask(with: url) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
receivedExpectation.fulfill()
}
dataTask.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
func testHelloEndToEndTLSwithCA() {
let config = createCASignedTLSConfig()
testHelloEndToEndInternal(config: config, selfsigned: false)
}
func testHelloEndToEndTLSwithSelfSigned() {
let config = createSelfSignedTLSConfig()
testHelloEndToEndInternal(config: config, selfsigned: true)
}
func testHelloEndToEndInternal(config: TLSConfiguration, selfsigned: Bool) {
let receivedExpectation = self.expectation(description: "Received web response \(#function)")
let urlStr: String
let session: URLSession
let server = HTTPServer(with: HTTPServer.Options(onPort: 0, tlsConf: config), requestHandler: HelloWorldHandler().handle)
do {
try server.start()
if selfsigned {
#if os(OSX)
session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue:OperationQueue.main)
#else
// delegate in URLSession in Linux is not implemented. Using this to compile but it will fail if run on Linux.
session = URLSession(configuration: URLSessionConfiguration.default)
#endif
urlStr = "localhost"
} else {
session = URLSession(configuration: URLSessionConfiguration.default)
urlStr = "ssl.gelareh.xyz"
}
let url = URL(string: "https://\(urlStr):\(server.port)/helloworld")!
print("Test \(#function) on port \(server.port)")
let dataTask = session.dataTask(with: url) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual("Hello, World!", String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil")
receivedExpectation.fulfill()
}
dataTask.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
func testSimpleHelloEndToEndTLSwithCA() {
let config = createCASignedTLSConfig()
let receivedExpectation = self.expectation(description: "Received web response \(#function)")
let session: URLSession
let simpleHelloWebApp = SimpleResponseCreator { (_, body) -> SimpleResponseCreator.Response in
return SimpleResponseCreator.Response(
status: .ok,
headers: ["X-foo": "bar"],
body: "Hello, World!".data(using: .utf8)!
)
}
let server = HTTPServer(with: HTTPServer.Options(onPort: 0, tlsConf: config), requestHandler: simpleHelloWebApp.handle)
do {
try server.start()
session = URLSession(configuration: URLSessionConfiguration.default)
let url = URL(string: "https://ssl.gelareh.xyz:\(server.port)/helloworld")!
print("Test \(#function) on port \(server.port)")
let dataTask = session.dataTask(with: url) { (responseBody, rawResponse, error) in
print("\(#function) dataTask returned")
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
let responseString = String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil"
XCTAssertEqual("Hello, World!", responseString)
print("\(#function) fulfilling expectation")
receivedExpectation.fulfill()
}
dataTask.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
func testRequestEchoEndToEndTLSwithCA() {
let config = createCASignedTLSConfig()
let receivedExpectation = self.expectation(description: "Received web response \(#function)")
let session: URLSession
let testString = "This is a test"
let server = HTTPServer(with: HTTPServer.Options(onPort: 0, tlsConf: config), requestHandler: EchoHandler().handle)
do {
try server.start()
session = URLSession(configuration: URLSessionConfiguration.default)
let url = URL(string: "https://ssl.gelareh.xyz:\(server.port)/echo")!
print("Test \(#function) on port \(server.port)")
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = testString.data(using: .utf8)
request.setValue("text/plain", forHTTPHeaderField: "Content-Type")
let dataTask = session.dataTask(with: request) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual(testString, String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil")
receivedExpectation.fulfill()
}
dataTask.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
func testRequestKeepAliveEchoEndToEndTLSwithCA() {
let config = createCASignedTLSConfig()
let receivedExpectation1 = self.expectation(description: "Received web response 1: \(#function)")
let receivedExpectation2 = self.expectation(description: "Received web response 2: \(#function)")
let receivedExpectation3 = self.expectation(description: "Received web response 3: \(#function)")
let testString1="This is a test"
let testString2="This is a test, too"
let testString3="This is also a test"
let session: URLSession
let server = HTTPServer(with: HTTPServer.Options(onPort: 0, tlsConf: config), requestHandler: EchoHandler().handle)
do {
try server.start()
session = URLSession(configuration: URLSessionConfiguration.default)
let url = URL(string: "https://ssl.gelareh.xyz:\(server.port)/echo")!
print("Test \(#function) on port \(server.port)")
var request1 = URLRequest(url: url)
request1.httpMethod = "POST"
request1.httpBody = testString1.data(using: .utf8)
request1.setValue("text/plain", forHTTPHeaderField: "Content-Type")
let dataTask1 = session.dataTask(with: request1) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
let headers = response?.allHeaderFields ?? ["": ""]
let connectionHeader: String = headers["Connection"] as? String ?? ""
XCTAssertEqual(connectionHeader, "Keep-Alive", "No Keep-Alive Connection")
XCTAssertNotNil(responseBody, "No Response Body")
XCTAssertEqual(server.connectionCount, 1)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual(testString1, String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil")
var request2 = URLRequest(url: url)
request2.httpMethod = "POST"
request2.httpBody = testString2.data(using: .utf8)
request2.setValue("text/plain", forHTTPHeaderField: "Content-Type")
let dataTask2 = session.dataTask(with: request2) { (responseBody2, rawResponse2, error2) in
let response2 = rawResponse2 as? HTTPURLResponse
XCTAssertNil(error2, "\(error2!.localizedDescription)")
XCTAssertNotNil(response2)
let headers = response2?.allHeaderFields ?? ["": ""]
let connectionHeader: String = headers["Connection"] as? String ?? ""
XCTAssertEqual(connectionHeader, "Keep-Alive", "No Keep-Alive Connection")
XCTAssertEqual(server.connectionCount, 1)
XCTAssertNotNil(responseBody2)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response2?.statusCode ?? 0)
XCTAssertEqual(testString2, String(data: responseBody2 ?? Data(), encoding: .utf8) ?? "Nil")
var request3 = URLRequest(url: url)
request3.httpMethod = "POST"
request3.httpBody = testString3.data(using: .utf8)
request3.setValue("text/plain", forHTTPHeaderField: "Content-Type")
let dataTask3 = session.dataTask(with: request3) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
let headers = response?.allHeaderFields ?? ["": ""]
let connectionHeader: String = headers["Connection"] as? String ?? ""
XCTAssertEqual(connectionHeader, "Keep-Alive", "No Keep-Alive Connection")
XCTAssertEqual(server.connectionCount, 1)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual(testString3, String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil")
receivedExpectation3.fulfill()
}
dataTask3.resume()
receivedExpectation2.fulfill()
}
dataTask2.resume()
receivedExpectation1.fulfill()
}
dataTask1.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
func testMultipleRequestWithoutKeepAliveEchoEndToEndTLSwithCA() {
let config = createCASignedTLSConfig()
let receivedExpectation1 = self.expectation(description: "Received web response 1: \(#function)")
let receivedExpectation2 = self.expectation(description: "Received web response 2: \(#function)")
let receivedExpectation3 = self.expectation(description: "Received web response 3: \(#function)")
let testString1="This is a test"
let testString2="This is a test, too"
let testString3="This is also a test"
let session: URLSession
let server = HTTPServer(with: HTTPServer.Options(onPort: 0, tlsConf: config), requestHandler: EchoHandler().handle)
do {
try server.start()
session = URLSession(configuration: URLSessionConfiguration.default)
let url1 = URL(string: "https://ssl.gelareh.xyz:\(server.port)/echo")!
print("Test \(#function) on port \(server.port)")
var request1 = URLRequest(url: url1)
request1.httpMethod = "POST"
request1.httpBody = testString1.data(using: .utf8)
request1.setValue("text/plain", forHTTPHeaderField: "Content-Type")
request1.setValue("Keep-Alive", forHTTPHeaderField: "Connection")
let dataTask1 = session.dataTask(with: request1) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
let headers = response?.allHeaderFields ?? ["": ""]
let connectionHeader: String = headers["Connection"] as? String ?? ""
let keepAliveHeader = headers["Connection"]
XCTAssertEqual(connectionHeader, "Keep-Alive", "No Keep-Alive Connection")
XCTAssertNotNil(keepAliveHeader)
XCTAssertNotNil(responseBody, "No Keep-Alive Header")
XCTAssertEqual(server.connectionCount, 1)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual(testString1, String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil")
let url2 = URL(string: "https://ssl1.gelareh.xyz:\(server.port)/echo")!
var request2 = URLRequest(url: url2)
request2.httpMethod = "POST"
request2.httpBody = testString2.data(using: .utf8)
request2.setValue("text/plain", forHTTPHeaderField: "Content-Type")
request2.setValue("Keep-Alive", forHTTPHeaderField: "Connection")
let dataTask2 = session.dataTask(with: request2) { (responseBody2, rawResponse2, error2) in
let response2 = rawResponse2 as? HTTPURLResponse
XCTAssertNil(error2, "\(error2!.localizedDescription)")
XCTAssertNotNil(response2)
let headers = response2?.allHeaderFields ?? ["": ""]
let connectionHeader: String = headers["Connection"] as? String ?? ""
let keepAliveHeader = headers["Connection"]
XCTAssertEqual(connectionHeader, "Keep-Alive", "No Keep-Alive Connection")
XCTAssertNotNil(keepAliveHeader, "No Keep-Alive Header")
XCTAssertEqual(server.connectionCount, 2)
XCTAssertNotNil(responseBody2)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response2?.statusCode ?? 0)
XCTAssertEqual(testString2, String(data: responseBody2 ?? Data(), encoding: .utf8) ?? "Nil")
let url3 = URL(string: "https://ssl2.gelareh.xyz:\(server.port)/echo")!
var request3 = URLRequest(url: url3)
request3.httpMethod = "POST"
request3.httpBody = testString3.data(using: .utf8)
request3.setValue("text/plain", forHTTPHeaderField: "Content-Type")
request3.setValue("Keep-Alive", forHTTPHeaderField: "Connection")
let dataTask3 = session.dataTask(with: request3) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
let headers = response?.allHeaderFields ?? ["": ""]
let connectionHeader: String = headers["Connection"] as? String ?? ""
let keepAliveHeader = headers["Connection"]
XCTAssertEqual(connectionHeader, "Keep-Alive", "No Keep-Alive Connection")
XCTAssertNotNil(keepAliveHeader, "No Keep-Alive Header")
XCTAssertEqual(server.connectionCount, 3)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual(testString3, String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil")
receivedExpectation3.fulfill()
}
dataTask3.resume()
receivedExpectation2.fulfill()
}
dataTask2.resume()
receivedExpectation1.fulfill()
}
dataTask1.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
func testRequestLargeEchoEndToEndTLSwithCA() {
let config = createCASignedTLSConfig()
let receivedExpectation = self.expectation(description: "Received web response \(#function)")
let session: URLSession
//Use a small chunk size to make sure that we're testing multiple HTTPBodyHandler calls
let chunkSize = 1024
// Get a file we know exists
let executableURL = URL(fileURLWithPath: CommandLine.arguments[0])
let testExecutableData: Data
do {
testExecutableData = try Data(contentsOf: executableURL)
} catch {
XCTFail("Could not create Data from contents of \(executableURL)")
return
}
var testDataLong = testExecutableData + testExecutableData + testExecutableData + testExecutableData
let length = testDataLong.count
let keep = 16385
let remove = length - keep
if remove > 0 {
testDataLong.removeLast(remove)
}
let testData = Data(testDataLong)
let server = PoCSocketSimpleServer()
do {
try server.start(port: 0, maxReadLength: chunkSize, tls: config, handler: EchoHandler().handle)
session = URLSession(configuration: URLSessionConfiguration.default)
let url = URL(string: "https://ssl.gelareh.xyz:\(server.port)/echo")!
print("Test \(#function) on port \(server.port)")
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = testData
let dataTask = session.dataTask(with: request) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual(testData, responseBody ?? Data())
receivedExpectation.fulfill()
}
dataTask.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
func testRequestLargePostHelloWorldTLSwithCA() {
let config = createCASignedTLSConfig()
let receivedExpectation = self.expectation(description: "Received web response \(#function)")
let session: URLSession
// Use a small chunk size to make sure that we stop after one HTTPBodyHandler call
let chunkSize = 1024
// Get a file we know exists
let executableURL = URL(fileURLWithPath: CommandLine.arguments[0])
let testExecutableData: Data
do {
testExecutableData = try Data(contentsOf: executableURL)
} catch {
XCTFail("Could not create Data from contents of \(executableURL)")
return
}
//Make sure there's data there
XCTAssertNotNil(testExecutableData)
let executableLength = testExecutableData.count
let server = PoCSocketSimpleServer()
do {
let testHandler = AbortAndSendHelloHandler()
try server.start(port: 0, maxReadLength: chunkSize, tls: config, handler: testHandler.handle)
session = URLSession(configuration: URLSessionConfiguration.default)
print("Test \(#function) on port \(server.port)")
let url = URL(string: "https://ssl.gelareh.xyz:\(server.port)/echo")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let uploadTask = session.uploadTask(with: request, fromFile: executableURL) { (responseBody, rawResponse, error) in
let response = rawResponse as? HTTPURLResponse
XCTAssertNil(error, "\(error!.localizedDescription)")
XCTAssertNotNil(response)
XCTAssertNotNil(responseBody)
XCTAssertEqual(Int(HTTPResponseStatus.ok.code), response?.statusCode ?? 0)
XCTAssertEqual("Hello, World!", String(data: responseBody ?? Data(), encoding: .utf8) ?? "Nil")
XCTAssertEqual(Int(testHandler.chunkCalledCount), 1)
XCTAssertLessThan(testHandler.chunkLength, executableLength, "Should have written less than the length of the file")
if (chunkSize < TLSConstants.maxTLSRecordLength) {
XCTAssertLessThanOrEqual(Int(testHandler.chunkLength), TLSConstants.maxTLSRecordLength)
} else {
XCTAssertLessThanOrEqual(Int(testHandler.chunkLength), chunkSize)
}
receivedExpectation.fulfill()
}
uploadTask.resume()
self.waitForExpectations(timeout: 10) { (error) in
if let error = error {
XCTFail("\(error)")
}
}
server.stop()
} catch {
XCTFail("Error listening on port \(0): \(error). Use server.failed(callback:) to handle")
}
}
private func createSelfSignedTLSConfig() -> TLSConfiguration {
#if os(Linux)
let myCAPath = URL(fileURLWithPath: #file).appendingPathComponent("../../Certs/Self-Signed/chain.pem").standardized
let myCertPath = URL(fileURLWithPath: #file).appendingPathComponent("../../Certs/Self-Signed/cert.pem").standardized
let myKeyPath = URL(fileURLWithPath: #file).appendingPathComponent("../../Certs/Self-Signed/key.pem").standardized
let config = TLSConfiguration(withCACertificateFilePath: myCAPath.path, usingCertificateFile: myCertPath.path, withKeyFile: myKeyPath.path, usingSelfSignedCerts: true)
#else
let myP12 = URL(fileURLWithPath: #file).appendingPathComponent("../../../Certs/Self-Signed/cert.pfx").standardized
let myPassword = "sw!ft!sC00l"
let config = TLSConfiguration(withChainFilePath: myP12.path, withPassword: myPassword, usingSelfSignedCerts: true)
#endif
return config
}
private func createCASignedTLSConfig() -> TLSConfiguration {
#if os(Linux)
let myCAPath = URL(fileURLWithPath: #file).appendingPathComponent("../../../Certs/letsEncryptCA/chain.pem").standardized
let myCertPath = URL(fileURLWithPath: #file).appendingPathComponent("../../../Certs/letsEncryptCA/cert.pem").standardized
let myKeyPath = URL(fileURLWithPath: #file).appendingPathComponent("../../../Certs/letsEncryptCA/key.pem").standardized
let config = TLSConfiguration(withCACertificateFilePath: myCAPath.path, usingCertificateFile: myCertPath.path, withKeyFile: myKeyPath.path, usingSelfSignedCerts: false)
// print("myCAPath is at: \(myCAPath.absoluteString) ")
// print("myCertPath is at: \(myCertPath.absoluteString) ")
// print("myKeyPath is at: \(myKeyPath.absoluteString) ")
#else
let myP12 = URL(fileURLWithPath: #file).appendingPathComponent("../../../Certs/letsEncryptCA/cert.pfx").standardized
let myPassword = "password"
let config = TLSConfiguration(withChainFilePath: myP12.path, withPassword: myPassword, usingSelfSignedCerts: false)
// print("myCertPath is at: \(myP12.absoluteString) ")
#endif
return config
}
static var allTests = [
("testOkEndToEndTLSwithCA", testOkEndToEndTLSwithCA),
("testHelloEndToEndTLSwithCA", testHelloEndToEndTLSwithCA),
("testSimpleHelloEndToEndTLSwithCA", testSimpleHelloEndToEndTLSwithCA),
("testRequestEchoEndToEndTLSwithCA", testRequestEchoEndToEndTLSwithCA),
("testRequestKeepAliveEchoEndToEndTLSwithCA", testRequestKeepAliveEchoEndToEndTLSwithCA),
("testRequestLargeEchoEndToEndTLSwithCA", testRequestLargeEchoEndToEndTLSwithCA),
("testMultipleRequestWithoutKeepAliveEchoEndToEndTLSwithCA", testMultipleRequestWithoutKeepAliveEchoEndToEndTLSwithCA),
("testRequestLargePostHelloWorldTLSwithCA", testRequestLargePostHelloWorldTLSwithCA),
]
}
#if os(OSX)
extension TLSServerTests: URLSessionDelegate {
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!) )
}
}
#endif
+19
View File
@@ -62,9 +62,28 @@ class VersionTests: XCTestCase {
XCTAssertFalse(version20 <= version11)
}
func testHashValue() {
XCTAssertEqual(version10.hashValue, HTTPVersion(major: 1, minor: 0).hashValue)
XCTAssertEqual(version11.hashValue, HTTPVersion(major: 1, minor: 1).hashValue)
XCTAssertEqual(version20.hashValue, HTTPVersion(major: 2, minor: 0).hashValue)
XCTAssertNotEqual(version10.hashValue, HTTPVersion(major: 1, minor: 1).hashValue)
XCTAssertNotEqual(version11.hashValue, HTTPVersion(major: 1, minor: 0).hashValue)
XCTAssertNotEqual(version20.hashValue, HTTPVersion(major: 1, minor: 0).hashValue)
XCTAssertNotEqual(version20.hashValue, HTTPVersion(major: 1, minor: 1).hashValue)
}
func testDescription() {
XCTAssertEqual(version10.description, "HTTP/1.0")
XCTAssertEqual(version11.description, "HTTP/1.1")
XCTAssertEqual(version20.description, "HTTP/2.0")
}
static var allTests = [
("testEquals", testEquals),
("testGreater", testGreater),
("testLess", testLess),
("testHashValue", testHashValue),
("testDescription", testDescription)
]
}
+2
View File
@@ -15,4 +15,6 @@ XCTMain([
testCase(HeadersTests.allTests),
testCase(ResponseTests.allTests),
testCase(ServerTests.allTests),
testCase(ServerTestsEndToEnd.allTests),
testCase(TLSServerTests.allTests),
])