Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07cd40ce8c | ||
|
|
8a5668cc19 | ||
|
|
21cf431cf1 | ||
|
|
6b8cd8b092 | ||
|
|
7bf242651c | ||
|
|
30344ad3f0 | ||
|
|
9b64bc21d8 | ||
|
|
b5001e49c6 | ||
|
|
3739ce6037 | ||
|
|
36adb9f32f | ||
|
|
66c6b6a06d | ||
|
|
b576f2e860 | ||
|
|
3af439047d | ||
|
|
933ecc536b | ||
|
|
dd00ed6e3b | ||
|
|
31d066a0e9 | ||
|
|
e002bc58eb | ||
|
|
d50eeecf76 | ||
|
|
4eab465956 | ||
|
|
92dcc60699 | ||
|
|
825b680c64 | ||
|
|
2e987e181c | ||
|
|
397c5fb7a4 | ||
|
|
b6211a2894 | ||
|
|
4d2c2b55f7 | ||
|
|
b54c7b40ad | ||
|
|
9dd0946bff | ||
|
|
a5a4da0375 | ||
|
|
6ad4650c17 | ||
|
|
2c4336880d | ||
|
|
b624e39530 | ||
|
|
53e1e38e1b | ||
|
|
630bd0bfc9 | ||
|
|
728b66d569 | ||
|
|
88ab09d047 | ||
|
|
473177815d | ||
|
|
fa11340bc1 | ||
|
|
34cd525151 | ||
|
|
d9dbb40531 | ||
|
|
1994157441 | ||
|
|
ec6087c2a1 | ||
|
|
fb6df9b933 | ||
|
|
5a00cde492 | ||
|
|
fffb8dd883 | ||
|
|
234551eeef |
@@ -73,4 +73,5 @@ fastlane/test_output
|
|||||||
*.orig
|
*.orig
|
||||||
/.idea
|
/.idea
|
||||||
/Package.pins
|
/Package.pins
|
||||||
|
/Package.resolved
|
||||||
docs
|
docs
|
||||||
|
|||||||
@@ -385,7 +385,7 @@ public struct HTTPMethod : Hashable, CustomStringConvertible, ExpressibleByInteg
|
|||||||
public static let trace
|
public static let trace
|
||||||
public static let copy
|
public static let copy
|
||||||
public static let lock
|
public static let lock
|
||||||
public static let mkol
|
public static let mkcol
|
||||||
public static let move
|
public static let move
|
||||||
public static let propfind
|
public static let propfind
|
||||||
public static let proppatch
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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-----
|
||||||
@@ -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.
@@ -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-----
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
sw!ft!sC00l
|
||||||
@@ -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.
@@ -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-----
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE REQUEST-----
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIJKgIBAAKCAgEAqUQThV3COQEj+YAInCcB8AZgaTV2KZSOz7HnwmdG9Vl8xBDl
|
||||||
|
/sQYLh7Nfatr2AKeAG/5Tjw/fvy4wJLFoYCafI07a/FZyLO/krn6J3b5EyUeRceW
|
||||||
|
sIqPVNqupR7ZbTYZ2wuIEFcpMGOIL7V1dHO1lVe51xtqBvN9uMpAigQ9tH5VgGvx
|
||||||
|
Ymwv8WllxcLYbHa4E7Tua0r5ZAajZSlk4TrmbTKDvj/8TKQv6HK3o9AS/OdrNJeO
|
||||||
|
lUr6UFLRjA84rYJBFPuQtRpUIgV/47ouDwjUSAQaV1z3XGGin0fWcFQwjDfFzQUy
|
||||||
|
jl3Xl/v37I6H3WYNZjUXzZGJ0ujxlo8OAOzL+Ivs2tPumVCoAJVIpapGq9dggNsX
|
||||||
|
X3P26tWrK+eYWWpe2y5/gi/kkKeeodDjznaub3DmxzqZWfKKSGbE8AEe7dykG1YN
|
||||||
|
iHKjDgVBeLQcB5+Ly83DgTU7mXMJwlSA++P+y+wJGf9iBIdmrZsQZkKxzMCIscYZ
|
||||||
|
oiqmw1yehk5Ng3U5TSeosdjRYaDLHYrOwNZLTf6noRyblAWHYM7M7+agX92yvvTv
|
||||||
|
ASmCwqtBn2xy9yT0EORzfALOgf6oCNwWVZ2dCT7z/lqV2DEFh45SATdXiAYJhiCA
|
||||||
|
Q1UvCKcfz9z66aliSGr/9IZN9/+jmXS7WGn8Z/9YnlNpkqSdGsBOeLmGRG0CAwEA
|
||||||
|
AQKCAgAfdzdFyJsta6fbXtC67olurFDJ3hVcP3CY7ZKO0hIf1AXckOOgi6WPFUzP
|
||||||
|
+sjue/YRUL+AqrSHD8XsjpxMnJKbasnMFC7Dn56SUR5OrdA5neHVyPebU/a3KHvk
|
||||||
|
2CpFIhSGYstldj6C703GyK3P+x6bZ5Z1hXQdXLBXZMnAnm82GXIdkHNr/36eGyC/
|
||||||
|
Naxz5VHs3+qeaW6ZCJ7hVGObdw6U6BoTiOOG/9KkHIdE3Y5aE08blLz9xdVojyzD
|
||||||
|
I6U+pAMjOmy5Le7L8EfI9rhPNS35QUIAUZjuRHwtrfYQJaZsuoV7ymaOjqVgbIe3
|
||||||
|
IvvgzXLaOC53mYGMgp8+hNNumpaEDQtUE2HonEJYi8rjhMlhlvh8OWbHCI9yjMRg
|
||||||
|
eMtk3fZ4dtHGFu0XUpZegPFPEMsBmOVELWu9GPs6RNa4U7LsTFBTipUasyKREgdW
|
||||||
|
CbLBu+6+lFe67deUh2150j9PJ9r8SyFUp9UwMMtZirTI6OMxoEylg6gd5Gtvckhz
|
||||||
|
M+VSGX8w/fKRzW7Ipf94TIQlaAkjHI68AB7Qw30uaT9YfbEOP8cMSy9ZBKe+JQ/A
|
||||||
|
EtYsrOjHtARJ7pOWbifnu4zNKg8C4LIDt8sv+en/1/MFXO8+VRVUBmlNzvY8rD1Y
|
||||||
|
5mBftmWdAmFnHCtQH+3VwE331UjpZlYaNTBDe4TwbJeIcTmmQQKCAQEA5JG0/FHa
|
||||||
|
lAvr5Yo8/TI0NqX6bwjn52V6HX5x2Z/wpgQp+WzpSKNCQEWBDsS3X0SfGMJDHFcx
|
||||||
|
X8ysmnLAXcUbBmgnVP2see5cqGQyhHUL/GtfThGAdcs8defkZJspoiFwA1UioJO5
|
||||||
|
JahxBwyxdq6i61ijr1/PjVNWc4wRCsvV/pymmqNKACgiFsW6KB3gq9mcZewK9iZ3
|
||||||
|
nB+H2gQ9PXrC/re9efHO/rUPMuFo3caFZZTTOUbh/Ilno4B//OzXR/mT0ti1YQly
|
||||||
|
5AUkMRwoMUmUGMGZ2IHy2AKEnkk6YvkbTSiTLgsVjgW/MqSjuoeInTwCqRex7Te4
|
||||||
|
3L98/bFGzpG1HQKCAQEAvZRnyWhsUhimOAY0EQ+14bLqEU1D7zFIdEibGVdU4Z49
|
||||||
|
W2/q3CKvbU0gsE5z/L8Y0d+HfKEKsuIvz78SRjds8L1E/V8CReFsyQJW6WO2LNXG
|
||||||
|
eWKMKAJlHuD+s7bOmPssVVj2x05jdoN03tsDKS5rC+DnRYeBnp8uAPdHN5otwDbF
|
||||||
|
FGU1alTGUgTrcboPVf4OhaKh66VxbGliLq35mXW3E90aWtNrByuyJohobfjrpDwd
|
||||||
|
kLK6XbxUdkvIwylk+ZPOL9gI/p1Q38bsSP+w49UxASUvt5CfXaHxhc0qzMrFf2Tm
|
||||||
|
5EYDLx3qsNbqgOf5KzsYwsDsKXUkENsIaRQ7dnU7kQKCAQEA4ndT4WSgs2sUcbwE
|
||||||
|
LcKeZT8S4Qzr6SNsWOi9mSiVYYhiplW41lWcAN5cvus93NC66ecgY2Yo74h1xBnK
|
||||||
|
a54t5q0vu1AUokL/34BXZVIrbBBev5UruIqD/zah3uS13YRP3Z1uz7dODPfV9Wwo
|
||||||
|
MTVbCuAqXksJ9DcMQzQWdqH8B2fi0vjTC1C4ZePHTJQ91UepZHr5aWY3wKKlEeh9
|
||||||
|
XPrTVVlsDPT/aPKwenNIWeSmqz1IA0ouAu+JlkBtj4aCzzeDtbcuD1UzVqWZdGc3
|
||||||
|
k44ZhGXeLtyiZlAoYkSf6wxydoKrQUWON8eN53mlF93OCCw7Xpqxmh5Jtb6s8xfA
|
||||||
|
1k8cKQKCAQEAqONc0oDEfx6WdbKRD+H/FmJdQ6yhqKUu9uj3w0uZwqVF4/+AKzx7
|
||||||
|
K9RaGrbJfZCAe/e2q8CL13DJRzng/czCsHTs0Qui6r44O5pp8uFxmd+YQOsaEUqQ
|
||||||
|
60FlppRk2MRqal4m+sdKtHnH1AEof7dqhdqGLdraDoWgZhvkxhQETgh86f5/54o+
|
||||||
|
YzMezOIEZ4c9SK/psBRjR0FaxlsW0S+dOYTvxZoy7uBuhQVggxgOVPF5JT8T6A2u
|
||||||
|
8PPylmp6Gh0iwlyjJrDfK3v7Y8zluRJj5bFLIS0lzDRJBfoQ9wBtkWBCkXoNvBva
|
||||||
|
yE+t+ciuoWS0WkukGArTZnC6vWHY1175AQKCAQEA2K5DSq6hEcmQGKHBogWW4Emo
|
||||||
|
kz+IyJVwmDPDGhiVWApMODhMVGKbRM4dVVf8CFNYZKyTpbfUdivs0xcALigopjTW
|
||||||
|
bdE/gH7B81yA7hW7UD3oFOMSlu+6xi4GcB43rML1K/6KUAd7a3pVNl3YxywF6dKa
|
||||||
|
e7Tl3Ye0oq1L7haNTgGOd/1C7u72/oKsnoZH7dSGZA/AvhkMCYXWSTGq5iMATUuQ
|
||||||
|
yDh59Twmn9a+bN6bZy/Kt/Lwg+3Xptb9uEkGlwaEvtVzXGa9FfHD4WKt6TavbHY/
|
||||||
|
tI/psRiolGcG5/i8lIQN1USbHdM1v1BPWlr8MDojqXn4n5gUIwtoEEWq4laPvw==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIJJwIBAAKCAgEAjKfEit347Xx0mvjc3cBOhk+fa4Qx/EH/SCz5eMxdBAJe9VfU
|
||||||
|
MKeKjlFHVVtO97Nmv28OPbE5w4LgVtfwjChav76L4uD0GRMsjRGQFXiHV+UtxJ7G
|
||||||
|
MCUUuCgm4IQDQvCJ0oKcJ4YP7UziJOGk8PkIdMjHdzLYaRSZookYXJaSxx/h/OEj
|
||||||
|
W+up7u++Nwh/RUCegLDtdOuFENzovutIzie8sa4lbD3e4QBdayZez2u8pfVMfRiO
|
||||||
|
pYkymCyNNkRwLlOBPZZBCQHfQWjCcGKOJdRq0h5WZ6Rxxza8HA03YoEqASnHJ/ok
|
||||||
|
RQ1Gy0M9osFswyYmaPp8lWMi14aXlMFHbbL+G4p23BhBeiTnDR4wLM/UjATHNVxk
|
||||||
|
KiBnqr2sUHc8dnFqNiUgoJWc2aRL1EToHWOdJ51s/ZLFraPPViOPpCHQG7SetY7a
|
||||||
|
dJFdgQoee4fk9o6Zo9q7izRm70QHHuKGFIjNxs7ZSL6bjYMwKHLK5G75Q3WCqmNd
|
||||||
|
irfsQYjUL/TYNJnazfHV3702YKBcugg08R8q69TNakhfnDgRl5FIMd6Bro3CgA8L
|
||||||
|
bGL4jjoAhx0PohvIlJhghGwQ8xQEfWLmL0pRWUEV4gBIfGxLGQpLqt97choOGdYY
|
||||||
|
GZt+nuzR7KZH175BKNhMI9xcqD6/fb0jNT+GBC2s7DcZSLDODeU0Hf1BXR0CAwEA
|
||||||
|
AQKCAgA7kvtuDeJXRDVnRizWR5N609KkCVPakmF7woDPp09mWVK6+75F9VB8QvTB
|
||||||
|
tHDX/UZxoqoXey8KHi7C2adq1dTKDfzV1Y3N6Q8fmOa8EVbR5aHi+5TZ43rJHUiK
|
||||||
|
I5/2BEd7wBI/s0qfqcbG9EOWRQRN4pSJaiG7MBx6eTK2VJhKeriPERSW5FQPfb/F
|
||||||
|
M3YkcOAxhb6tnOu5Xre91Y36s136q9Xx6Kq0BiGLNq9Fj05RxHnnKjWQ63Fgfs44
|
||||||
|
w/f3xyqgWTmmhQJ4g9SHIVcvsq6j3HGgaEhApnA6OWbR8/9EitttlUczcBIiGZLL
|
||||||
|
kr1sUoZGRIHsDBc/ziP5v3tvfTae9qhqPN0Nnkpl/VLkUieS0iee5f8nCF7NgqMH
|
||||||
|
lPTKzPrsV+6Jq0BwHxh3trKRdfHxocIEQlzmn9MokX7iTWzcirmo3E/rpaCcSlys
|
||||||
|
H5sQtMiN1wtvPCIC4yCPRJf8dkiOK/CjMuEw+bsrO486agVLUOfbDfWWesHQ2Oqm
|
||||||
|
VQSfdLN/WeIzq9rFLp7SywBA0aeNEd4xP55iua+PiTH9nHpcJVk8PFiPwL9Q+Y5p
|
||||||
|
ZFV7CoUGzMJvqbusQca9GX1/kycfDrxrqM7Mb24fmLpmu9LEfQ6cwf04f39KTL1D
|
||||||
|
Js2D+6vwSKmE/21z8JrPUyaizGS3VqRtFGKSYVmk+0f8hZQmyQKCAQEAyBIkBPMe
|
||||||
|
booWLOdo+PpRkwjetbq/zOv3izfd0EG6GL1dy3TO0bzVeEmQ1nRHyOr+23leNxto
|
||||||
|
25uOoZ2yceRiK4i01VKRy4Gc6htegC0/UNpywTxIQEfWQeDZmvHDWpEpZ20GY3ER
|
||||||
|
xBd4xwwZNvyh/j5Jq7hPkDFprCQJ8vouERhQh071HlnE0f8zVfK+t4UdpN6F/5wc
|
||||||
|
kZP8j7C2FRWQn0XjsQeXO2PGZOlNphNYGda4nlaB9b21oYCiFni+TI44fkbEQQCX
|
||||||
|
LervYGy08fTT+Xe3MSGTwo/RwoR4YEubBeGZOCQUjdCl2FT1SVce930RfEQxajH6
|
||||||
|
8Ud7IPI0nvDAQwKCAQEAs/meS7fEisbyoQC9cpXvd6RSNZL+uLrk5Ljb3QnmXdLs
|
||||||
|
D2vfsXKIIfghKwhtStWBBTSyXxqI/EZeTxjPUjC6cW/7ZPPh6P6bCVWPBLystd5N
|
||||||
|
kGtpeJU6tzGOLr2bJKLL9HchntZpbASqE0YIpd3/3cGCjCVYz9FQItI0ghuEaPhP
|
||||||
|
9F8iaqO22jBJIMplvRBNCi7LOBL+dcGHFBi4nVH+K/hYN2Itt1Bxpg+wqzbNhXn0
|
||||||
|
KQahWNZnSTUNCv9CPssYJf5Lkm++c5D1h55MBxOznhP2JQJg6eLG7jZcpQIrVX1Z
|
||||||
|
r76TQSuHWh/xCV6BRaj2RvucYHx7Gv6vWRnLlWLHHwKCAQBr2VuRT30YGVfa3OO5
|
||||||
|
UzamDCIB1KmPzaOjaHopyUSIEYx+IHlclm58aSuqbwRDSmoX5VTkX+Imf7Rjap7G
|
||||||
|
xlYlIYxZciklirkLebV3Yuy+qQMzJ0vLWu4klRC4dZrZN2caWasX79uj2QNCSUNO
|
||||||
|
YODyLKGJ79Dz6a8NHGBAmpw6muDOD8ISmlF+4hLKQhCM3TUdqtaQ85Zy41NCIgAF
|
||||||
|
XZqYQRR8WZssaJyJyToSTFsxko3yzK9ByQIgDTdS5cOvgPrzFzKz152nIv3m/LKN
|
||||||
|
u2yJDf+yfGcqelyYftBlGFx4zyPJH6n0yISeGS4gWtZWkxTZ5+i7VjXv2piFKgsz
|
||||||
|
opQBAoIBAEmXcmDnvdg5TZEEKnMmWAsGCA+cEcgbs+jKpiyWFbqbuqb7pzZ57Kxn
|
||||||
|
N7jO81G8R8uHJsC4qvbtFzckn/Gtty8XaSZ4ixGoumBwudBoDf837WN2aGREMQQj
|
||||||
|
oU3/febXIqrN49N9PRJMPfcvle2s/ykALY983fnsTuZOKeZhthzuHFOCayJL20MQ
|
||||||
|
p3ZfDIbomXfmdnZxXdds0P0otqStmE8Gd5v960+f9zi+BbGc8SD5Ixt7eJJI6WcH
|
||||||
|
6FNs2PuwNCmk9+XjB29eAOf5lID4T2P+KZIsjNBWSJ2zYOKgJnQIk5nHRZNKZ0g0
|
||||||
|
82yvVhDT7BBOZj0V6Yi6R50ZbSOihmUCggEAIfAL588EVaF3lxBnf5L/kCEaeXMO
|
||||||
|
zAVg4DUGgKWBQTyaKplOP01PcmfgPzDBZk0HMBfF+w6R849ufeuO9mIAIP4N4+Q/
|
||||||
|
J7gVKLz6vKZLoPpGXmk/fxCz6d/iH5a4R4KS2IGz/B40DC0vg5wIOgM5VeC5XYxo
|
||||||
|
o8tlDHsL0nmeK6klIZswQgnln1hkgYR8DWdhdtCpOyGoxk+ZObZHYdCShgDzrJHe
|
||||||
|
lNADdNopXdsbmVIOGklgbQDNqAdoZIEMSERevRD4tzi8tVTyNRHH4tzWzvf2Equ7
|
||||||
|
SD9y196c5G8PHQcOX8TG7Q07jojsAcyudjfJ6yITMBVuxiCTFsgn2doyfA==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
|
||||||
+5
-1
@@ -14,6 +14,10 @@ let package = Package(
|
|||||||
dependencies: [
|
dependencies: [
|
||||||
// Dependencies declare other packages that this package depends on.
|
// Dependencies declare other packages that this package depends on.
|
||||||
// .package(url: /* package url */, from: "1.0.0"),
|
// .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: [
|
||||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
// 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: []),
|
dependencies: []),
|
||||||
.target(
|
.target(
|
||||||
name: "HTTP",
|
name: "HTTP",
|
||||||
dependencies: ["CHTTPParser"]),
|
dependencies: ["CHTTPParser", "ServerSecurity", "TLSService"]),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "HTTPTests",
|
name: "HTTPTests",
|
||||||
dependencies: ["HTTP"]),
|
dependencies: ["HTTP"]),
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# Swift Server Project HTTP APIs
|
# 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
|
## Getting Started
|
||||||
|
|
||||||
@@ -22,7 +24,7 @@ func hello(request: HTTPRequest, response: HTTPResponseWriter ) -> HTTPBodyProce
|
|||||||
let server = HTTPServer()
|
let server = HTTPServer()
|
||||||
try! server.start(port: 8080, handler: hello)
|
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.
|
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()
|
let server = HTTPServer()
|
||||||
try! server.start(port: 8080, handler: echo)
|
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.
|
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,6 +70,13 @@ When the response is complete, `response.done()` should be called.
|
|||||||
Full Jazzy documentation of the API is available here:
|
Full Jazzy documentation of the API is available here:
|
||||||
<https://swift-server.github.io/http/>
|
<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
|
## Acknowledgements
|
||||||
This project is based on an inital proposal from @weissi on the swift-server-dev mailing list:
|
This project is based on an inital proposal from @weissi on the swift-server-dev mailing list:
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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.
|
||||||
@@ -286,10 +286,10 @@ enum state
|
|||||||
, s_res_HT
|
, s_res_HT
|
||||||
, s_res_HTT
|
, s_res_HTT
|
||||||
, s_res_HTTP
|
, s_res_HTTP
|
||||||
|
, s_res_first_http_major
|
||||||
, s_res_http_major
|
, s_res_http_major
|
||||||
, s_res_http_dot
|
, s_res_first_http_minor
|
||||||
, s_res_http_minor
|
, s_res_http_minor
|
||||||
, s_res_http_end
|
|
||||||
, s_res_first_status_code
|
, s_res_first_status_code
|
||||||
, s_res_status_code
|
, s_res_status_code
|
||||||
, s_res_status_start
|
, s_res_status_start
|
||||||
@@ -316,10 +316,10 @@ enum state
|
|||||||
, s_req_http_HT
|
, s_req_http_HT
|
||||||
, s_req_http_HTT
|
, s_req_http_HTT
|
||||||
, s_req_http_HTTP
|
, s_req_http_HTTP
|
||||||
|
, s_req_first_http_major
|
||||||
, s_req_http_major
|
, s_req_http_major
|
||||||
, s_req_http_dot
|
, s_req_first_http_minor
|
||||||
, s_req_http_minor
|
, s_req_http_minor
|
||||||
, s_req_http_end
|
|
||||||
, s_req_line_almost_done
|
, s_req_line_almost_done
|
||||||
|
|
||||||
, s_header_field_start
|
, s_header_field_start
|
||||||
@@ -795,48 +795,75 @@ size_t http_parser_execute (http_parser *parser,
|
|||||||
|
|
||||||
case s_res_HTTP:
|
case s_res_HTTP:
|
||||||
STRICT_CHECK(ch != '/');
|
STRICT_CHECK(ch != '/');
|
||||||
UPDATE_STATE(s_res_http_major);
|
UPDATE_STATE(s_res_first_http_major);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case s_res_http_major:
|
case s_res_first_http_major:
|
||||||
if (UNLIKELY(!IS_NUM(ch))) {
|
if (UNLIKELY(ch < '0' || ch > '9')) {
|
||||||
SET_ERRNO(HPE_INVALID_VERSION);
|
SET_ERRNO(HPE_INVALID_VERSION);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
parser->http_major = ch - '0';
|
parser->http_major = ch - '0';
|
||||||
UPDATE_STATE(s_res_http_dot);
|
UPDATE_STATE(s_res_http_major);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case s_res_http_dot:
|
/* major HTTP version or dot */
|
||||||
|
case s_res_http_major:
|
||||||
{
|
{
|
||||||
if (UNLIKELY(ch != '.')) {
|
if (ch == '.') {
|
||||||
|
UPDATE_STATE(s_res_first_http_minor);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IS_NUM(ch)) {
|
||||||
|
SET_ERRNO(HPE_INVALID_VERSION);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
parser->http_major *= 10;
|
||||||
|
parser->http_major += ch - '0';
|
||||||
|
|
||||||
|
if (UNLIKELY(parser->http_major > 999)) {
|
||||||
SET_ERRNO(HPE_INVALID_VERSION);
|
SET_ERRNO(HPE_INVALID_VERSION);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
UPDATE_STATE(s_res_http_minor);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case s_res_http_minor:
|
/* first digit of minor HTTP version */
|
||||||
|
case s_res_first_http_minor:
|
||||||
if (UNLIKELY(!IS_NUM(ch))) {
|
if (UNLIKELY(!IS_NUM(ch))) {
|
||||||
SET_ERRNO(HPE_INVALID_VERSION);
|
SET_ERRNO(HPE_INVALID_VERSION);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
parser->http_minor = ch - '0';
|
parser->http_minor = ch - '0';
|
||||||
UPDATE_STATE(s_res_http_end);
|
UPDATE_STATE(s_res_http_minor);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case s_res_http_end:
|
/* minor HTTP version or end of request line */
|
||||||
|
case s_res_http_minor:
|
||||||
{
|
{
|
||||||
if (UNLIKELY(ch != ' ')) {
|
if (ch == ' ') {
|
||||||
|
UPDATE_STATE(s_res_first_status_code);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UNLIKELY(!IS_NUM(ch))) {
|
||||||
|
SET_ERRNO(HPE_INVALID_VERSION);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
parser->http_minor *= 10;
|
||||||
|
parser->http_minor += ch - '0';
|
||||||
|
|
||||||
|
if (UNLIKELY(parser->http_minor > 999)) {
|
||||||
SET_ERRNO(HPE_INVALID_VERSION);
|
SET_ERRNO(HPE_INVALID_VERSION);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
UPDATE_STATE(s_res_first_status_code);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -863,9 +890,10 @@ size_t http_parser_execute (http_parser *parser,
|
|||||||
UPDATE_STATE(s_res_status_start);
|
UPDATE_STATE(s_res_status_start);
|
||||||
break;
|
break;
|
||||||
case CR:
|
case CR:
|
||||||
|
UPDATE_STATE(s_res_line_almost_done);
|
||||||
|
break;
|
||||||
case LF:
|
case LF:
|
||||||
UPDATE_STATE(s_res_status_start);
|
UPDATE_STATE(s_header_field_start);
|
||||||
REEXECUTE();
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
SET_ERRNO(HPE_INVALID_STATUS);
|
SET_ERRNO(HPE_INVALID_STATUS);
|
||||||
@@ -887,13 +915,19 @@ size_t http_parser_execute (http_parser *parser,
|
|||||||
|
|
||||||
case s_res_status_start:
|
case s_res_status_start:
|
||||||
{
|
{
|
||||||
|
if (ch == CR) {
|
||||||
|
UPDATE_STATE(s_res_line_almost_done);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == LF) {
|
||||||
|
UPDATE_STATE(s_header_field_start);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
MARK(status);
|
MARK(status);
|
||||||
UPDATE_STATE(s_res_status);
|
UPDATE_STATE(s_res_status);
|
||||||
parser->index = 0;
|
parser->index = 0;
|
||||||
|
|
||||||
if (ch == CR || ch == LF)
|
|
||||||
REEXECUTE();
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -973,7 +1007,7 @@ size_t http_parser_execute (http_parser *parser,
|
|||||||
UPDATE_STATE(s_req_spaces_before_url);
|
UPDATE_STATE(s_req_spaces_before_url);
|
||||||
} else if (ch == matcher[parser->index]) {
|
} else if (ch == matcher[parser->index]) {
|
||||||
; /* nada */
|
; /* nada */
|
||||||
} else if ((ch >= 'A' && ch <= 'Z') || ch == '-') {
|
} else if (IS_ALPHA(ch)) {
|
||||||
|
|
||||||
switch (parser->method << 16 | parser->index << 8 | ch) {
|
switch (parser->method << 16 | parser->index << 8 | ch) {
|
||||||
#define XX(meth, pos, ch, new_meth) \
|
#define XX(meth, pos, ch, new_meth) \
|
||||||
@@ -982,27 +1016,31 @@ parser->method = HTTP_##new_meth; break;
|
|||||||
|
|
||||||
XX(POST, 1, 'U', PUT)
|
XX(POST, 1, 'U', PUT)
|
||||||
XX(POST, 1, 'A', PATCH)
|
XX(POST, 1, 'A', PATCH)
|
||||||
XX(POST, 1, 'R', PROPFIND)
|
|
||||||
XX(PUT, 2, 'R', PURGE)
|
|
||||||
XX(CONNECT, 1, 'H', CHECKOUT)
|
XX(CONNECT, 1, 'H', CHECKOUT)
|
||||||
XX(CONNECT, 2, 'P', COPY)
|
XX(CONNECT, 2, 'P', COPY)
|
||||||
XX(MKCOL, 1, 'O', MOVE)
|
XX(MKCOL, 1, 'O', MOVE)
|
||||||
XX(MKCOL, 1, 'E', MERGE)
|
XX(MKCOL, 1, 'E', MERGE)
|
||||||
XX(MKCOL, 1, '-', MSEARCH)
|
|
||||||
XX(MKCOL, 2, 'A', MKACTIVITY)
|
XX(MKCOL, 2, 'A', MKACTIVITY)
|
||||||
XX(MKCOL, 3, 'A', MKCALENDAR)
|
XX(MKCOL, 3, 'A', MKCALENDAR)
|
||||||
XX(SUBSCRIBE, 1, 'E', SEARCH)
|
XX(SUBSCRIBE, 1, 'E', SEARCH)
|
||||||
XX(REPORT, 2, 'B', REBIND)
|
XX(REPORT, 2, 'B', REBIND)
|
||||||
|
XX(POST, 1, 'R', PROPFIND)
|
||||||
XX(PROPFIND, 4, 'P', PROPPATCH)
|
XX(PROPFIND, 4, 'P', PROPPATCH)
|
||||||
|
XX(PUT, 2, 'R', PURGE)
|
||||||
XX(LOCK, 1, 'I', LINK)
|
XX(LOCK, 1, 'I', LINK)
|
||||||
XX(UNLOCK, 2, 'S', UNSUBSCRIBE)
|
XX(UNLOCK, 2, 'S', UNSUBSCRIBE)
|
||||||
XX(UNLOCK, 2, 'B', UNBIND)
|
XX(UNLOCK, 2, 'B', UNBIND)
|
||||||
XX(UNLOCK, 3, 'I', UNLINK)
|
XX(UNLOCK, 3, 'I', UNLINK)
|
||||||
#undef XX
|
#undef XX
|
||||||
|
|
||||||
default:
|
default:
|
||||||
SET_ERRNO(HPE_INVALID_METHOD);
|
SET_ERRNO(HPE_INVALID_METHOD);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
} else if (ch == '-' &&
|
||||||
|
parser->index == 1 &&
|
||||||
|
parser->method == HTTP_MKCOL) {
|
||||||
|
parser->method = HTTP_MSEARCH;
|
||||||
} else {
|
} else {
|
||||||
SET_ERRNO(HPE_INVALID_METHOD);
|
SET_ERRNO(HPE_INVALID_METHOD);
|
||||||
goto error;
|
goto error;
|
||||||
@@ -1115,41 +1153,57 @@ parser->method = HTTP_##new_meth; break;
|
|||||||
|
|
||||||
case s_req_http_HTTP:
|
case s_req_http_HTTP:
|
||||||
STRICT_CHECK(ch != '/');
|
STRICT_CHECK(ch != '/');
|
||||||
UPDATE_STATE(s_req_http_major);
|
UPDATE_STATE(s_req_first_http_major);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case s_req_http_major:
|
/* first digit of major HTTP version */
|
||||||
if (UNLIKELY(!IS_NUM(ch))) {
|
case s_req_first_http_major:
|
||||||
|
if (UNLIKELY(ch < '1' || ch > '9')) {
|
||||||
SET_ERRNO(HPE_INVALID_VERSION);
|
SET_ERRNO(HPE_INVALID_VERSION);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
parser->http_major = ch - '0';
|
parser->http_major = ch - '0';
|
||||||
UPDATE_STATE(s_req_http_dot);
|
UPDATE_STATE(s_req_http_major);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case s_req_http_dot:
|
/* major HTTP version or dot */
|
||||||
|
case s_req_http_major:
|
||||||
{
|
{
|
||||||
if (UNLIKELY(ch != '.')) {
|
if (ch == '.') {
|
||||||
|
UPDATE_STATE(s_req_first_http_minor);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UNLIKELY(!IS_NUM(ch))) {
|
||||||
|
SET_ERRNO(HPE_INVALID_VERSION);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
parser->http_major *= 10;
|
||||||
|
parser->http_major += ch - '0';
|
||||||
|
|
||||||
|
if (UNLIKELY(parser->http_major > 999)) {
|
||||||
SET_ERRNO(HPE_INVALID_VERSION);
|
SET_ERRNO(HPE_INVALID_VERSION);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
UPDATE_STATE(s_req_http_minor);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case s_req_http_minor:
|
/* first digit of minor HTTP version */
|
||||||
|
case s_req_first_http_minor:
|
||||||
if (UNLIKELY(!IS_NUM(ch))) {
|
if (UNLIKELY(!IS_NUM(ch))) {
|
||||||
SET_ERRNO(HPE_INVALID_VERSION);
|
SET_ERRNO(HPE_INVALID_VERSION);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
parser->http_minor = ch - '0';
|
parser->http_minor = ch - '0';
|
||||||
UPDATE_STATE(s_req_http_end);
|
UPDATE_STATE(s_req_http_minor);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case s_req_http_end:
|
/* minor HTTP version or end of request line */
|
||||||
|
case s_req_http_minor:
|
||||||
{
|
{
|
||||||
if (ch == CR) {
|
if (ch == CR) {
|
||||||
UPDATE_STATE(s_req_line_almost_done);
|
UPDATE_STATE(s_req_line_almost_done);
|
||||||
@@ -1161,8 +1215,21 @@ parser->method = HTTP_##new_meth; break;
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* XXX allow spaces after digit? */
|
||||||
|
|
||||||
|
if (UNLIKELY(!IS_NUM(ch))) {
|
||||||
SET_ERRNO(HPE_INVALID_VERSION);
|
SET_ERRNO(HPE_INVALID_VERSION);
|
||||||
goto error;
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
parser->http_minor *= 10;
|
||||||
|
parser->http_minor += ch - '0';
|
||||||
|
|
||||||
|
if (UNLIKELY(parser->http_minor > 999)) {
|
||||||
|
SET_ERRNO(HPE_INVALID_VERSION);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1727,17 +1794,10 @@ parser->method = HTTP_##new_meth; break;
|
|||||||
UPDATE_STATE(s_headers_done);
|
UPDATE_STATE(s_headers_done);
|
||||||
|
|
||||||
/* Set this here so that on_headers_complete() callbacks can see it */
|
/* Set this here so that on_headers_complete() callbacks can see it */
|
||||||
if ((parser->flags & F_UPGRADE) &&
|
|
||||||
(parser->flags & F_CONNECTION_UPGRADE)) {
|
|
||||||
/* For responses, "Upgrade: foo" and "Connection: upgrade" are
|
|
||||||
* mandatory only when it is a 101 Switching Protocols response,
|
|
||||||
* otherwise it is purely informational, to announce support.
|
|
||||||
*/
|
|
||||||
parser->upgrade =
|
parser->upgrade =
|
||||||
(parser->type == HTTP_REQUEST || parser->status_code == 101);
|
((parser->flags & (F_UPGRADE | F_CONNECTION_UPGRADE)) ==
|
||||||
} else {
|
(F_UPGRADE | F_CONNECTION_UPGRADE) ||
|
||||||
parser->upgrade = (parser->method == HTTP_CONNECT);
|
parser->method == HTTP_CONNECT);
|
||||||
}
|
|
||||||
|
|
||||||
/* Here we call the headers_complete callback. This is somewhat
|
/* Here we call the headers_complete callback. This is somewhat
|
||||||
* different than other callbacks because if the user returns 1, we
|
* different than other callbacks because if the user returns 1, we
|
||||||
@@ -1756,7 +1816,6 @@ parser->method = HTTP_##new_meth; break;
|
|||||||
case 2:
|
case 2:
|
||||||
parser->upgrade = 1;
|
parser->upgrade = 1;
|
||||||
|
|
||||||
/* FALLTHROUGH */
|
|
||||||
case 1:
|
case 1:
|
||||||
parser->flags |= F_SKIPBODY;
|
parser->flags |= F_SKIPBODY;
|
||||||
break;
|
break;
|
||||||
@@ -2316,7 +2375,7 @@ http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
|
|||||||
case s_req_server_with_at:
|
case s_req_server_with_at:
|
||||||
found_at = 1;
|
found_at = 1;
|
||||||
|
|
||||||
/* FALLTHROUGH */
|
/* FALLTROUGH */
|
||||||
case s_req_server:
|
case s_req_server:
|
||||||
uf = UF_HOST;
|
uf = UF_HOST;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -29,10 +29,11 @@ extern "C" {
|
|||||||
#define HTTP_PARSER_VERSION_MINOR 7
|
#define HTTP_PARSER_VERSION_MINOR 7
|
||||||
#define HTTP_PARSER_VERSION_PATCH 1
|
#define HTTP_PARSER_VERSION_PATCH 1
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <sys/types.h>
|
||||||
#if defined(_WIN32) && !defined(__MINGW32__) && \
|
#if defined(_WIN32) && !defined(__MINGW32__) && \
|
||||||
(!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__)
|
(!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__)
|
||||||
#include <BaseTsd.h>
|
#include <BaseTsd.h>
|
||||||
|
#include <stddef.h>
|
||||||
typedef __int8 int8_t;
|
typedef __int8 int8_t;
|
||||||
typedef unsigned __int8 uint8_t;
|
typedef unsigned __int8 uint8_t;
|
||||||
typedef __int16 int16_t;
|
typedef __int16 int16_t;
|
||||||
@@ -89,76 +90,6 @@ extern "C" {
|
|||||||
typedef int (*http_cb) (http_parser*);
|
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) \
|
#define HTTP_METHOD_MAP(XX) \
|
||||||
XX(0, DELETE, DELETE) \
|
XX(0, DELETE, DELETE) \
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ extension HTTPMethod {
|
|||||||
/// LOCK method.
|
/// LOCK method.
|
||||||
public static let lock = HTTPMethod("LOCK")
|
public static let lock = HTTPMethod("LOCK")
|
||||||
/// MKCOL method.
|
/// MKCOL method.
|
||||||
public static let mkol = HTTPMethod("MKCOL")
|
public static let mkcol = HTTPMethod("MKCOL")
|
||||||
/// MOVE method.
|
/// MOVE method.
|
||||||
public static let move = HTTPMethod("MOVE")
|
public static let move = HTTPMethod("MOVE")
|
||||||
/// PROPFIND method.
|
/// PROPFIND method.
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public typealias HTTPBodyHandler = (HTTPBodyChunk, inout Bool) -> Void
|
|||||||
public enum HTTPBodyProcessing {
|
public enum HTTPBodyProcessing {
|
||||||
/// Used to discard the body data associated with the incoming HTTP request
|
/// Used to discard the body data associated with the incoming HTTP request
|
||||||
case discardBody
|
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)
|
case processBody(handler: HTTPBodyHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,35 +6,44 @@
|
|||||||
// See http://swift.org/LICENSE.txt for license information
|
// See http://swift.org/LICENSE.txt for license information
|
||||||
//
|
//
|
||||||
|
|
||||||
/// Definition of an HTTP server.
|
import ServerSecurity
|
||||||
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 }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A basic HTTP server. Currently this is implemented using the PoCSocket
|
/// A basic HTTP server. Currently this is implemented using the PoCSocket
|
||||||
/// abstraction, but the intention is to remove this dependency and reimplement
|
/// abstraction, but the intention is to remove this dependency and reimplement
|
||||||
/// the class using transport APIs provided by the Server APIs working group.
|
/// the class using transport APIs provided by the Server APIs working group.
|
||||||
public class HTTPServer: HTTPServing {
|
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()
|
private let server = PoCSocketSimpleServer()
|
||||||
|
|
||||||
/// Create an instance of the server. This needs to be followed with a call to `start(port:handler:)`
|
/// 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.
|
/// 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 {
|
public func start() throws {
|
||||||
try server.start(port: port, handler: handler)
|
try server.start(port: options.port, tls: options.tlsConfig, handler: handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stop the server
|
/// Stop the server
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ import CHTTPParser
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Dispatch
|
import Dispatch
|
||||||
|
|
||||||
|
public enum StreamingParserError: Error {
|
||||||
|
case ConnectionAbortedError
|
||||||
|
}
|
||||||
|
|
||||||
/// Class that wraps the CHTTPParser and calls the `HTTPRequestHandler` to get the response
|
/// Class that wraps the CHTTPParser and calls the `HTTPRequestHandler` to get the response
|
||||||
/// :nodoc:
|
/// :nodoc:
|
||||||
public class StreamingParser: HTTPResponseWriter {
|
public class StreamingParser: HTTPResponseWriter {
|
||||||
@@ -42,12 +46,35 @@ public class StreamingParser: HTTPResponseWriter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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.
|
/// Optional delegate that can tell us how many connections are in-flight.
|
||||||
public weak var connectionCounter: CurrentConnectionCounting?
|
public weak var connectionCounter: CurrentConnectionCounting?
|
||||||
|
|
||||||
/// Holds the bytes that come from the CHTTPParser until we have enough of them to do something with it
|
/// Holds the bytes that come from the CHTTPParser until we have enough of them to do something with it
|
||||||
var parserBuffer: Data?
|
var parserBuffer: Data?
|
||||||
|
|
||||||
|
/// Lock for parser buffer.
|
||||||
|
private let _parserBufferLock = DispatchSemaphore(value: 1)
|
||||||
|
|
||||||
/// HTTP Parser
|
/// HTTP Parser
|
||||||
var httpParser = http_parser()
|
var httpParser = http_parser()
|
||||||
var httpParserSettings = http_parser_settings()
|
var httpParserSettings = http_parser_settings()
|
||||||
@@ -189,6 +216,8 @@ public class StreamingParser: HTTPResponseWriter {
|
|||||||
if lastCallBack == currentCallBack {
|
if lastCallBack == currentCallBack {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
_parserBufferLock.wait()
|
||||||
|
defer { _parserBufferLock.signal() }
|
||||||
switch lastCallBack {
|
switch lastCallBack {
|
||||||
case .headerFieldReceived:
|
case .headerFieldReceived:
|
||||||
if let parserBuffer = self.parserBuffer {
|
if let parserBuffer = self.parserBuffer {
|
||||||
@@ -274,6 +303,9 @@ public class StreamingParser: HTTPResponseWriter {
|
|||||||
func headerFieldReceived(data: UnsafePointer<Int8>?, length: Int) -> Int32 {
|
func headerFieldReceived(data: UnsafePointer<Int8>?, length: Int) -> Int32 {
|
||||||
processCurrentCallback(.headerFieldReceived)
|
processCurrentCallback(.headerFieldReceived)
|
||||||
guard let data = data else { return 0 }
|
guard let data = data else { return 0 }
|
||||||
|
_parserBufferLock.wait()
|
||||||
|
defer { _parserBufferLock.signal() }
|
||||||
|
|
||||||
data.withMemoryRebound(to: UInt8.self, capacity: length) { (ptr) -> Void in
|
data.withMemoryRebound(to: UInt8.self, capacity: length) { (ptr) -> Void in
|
||||||
if var parserBuffer = parserBuffer {
|
if var parserBuffer = parserBuffer {
|
||||||
parserBuffer.append(ptr, count: length)
|
parserBuffer.append(ptr, count: length)
|
||||||
@@ -287,6 +319,9 @@ public class StreamingParser: HTTPResponseWriter {
|
|||||||
func headerValueReceived(data: UnsafePointer<Int8>?, length: Int) -> Int32 {
|
func headerValueReceived(data: UnsafePointer<Int8>?, length: Int) -> Int32 {
|
||||||
processCurrentCallback(.headerValueReceived)
|
processCurrentCallback(.headerValueReceived)
|
||||||
guard let data = data else { return 0 }
|
guard let data = data else { return 0 }
|
||||||
|
_parserBufferLock.wait()
|
||||||
|
defer { _parserBufferLock.signal() }
|
||||||
|
|
||||||
data.withMemoryRebound(to: UInt8.self, capacity: length) { (ptr) -> Void in
|
data.withMemoryRebound(to: UInt8.self, capacity: length) { (ptr) -> Void in
|
||||||
if var parserBuffer = parserBuffer {
|
if var parserBuffer = parserBuffer {
|
||||||
parserBuffer.append(ptr, count: length)
|
parserBuffer.append(ptr, count: length)
|
||||||
@@ -332,6 +367,9 @@ public class StreamingParser: HTTPResponseWriter {
|
|||||||
func urlReceived(data: UnsafePointer<Int8>?, length: Int) -> Int32 {
|
func urlReceived(data: UnsafePointer<Int8>?, length: Int) -> Int32 {
|
||||||
processCurrentCallback(.urlReceived)
|
processCurrentCallback(.urlReceived)
|
||||||
guard let data = data else { return 0 }
|
guard let data = data else { return 0 }
|
||||||
|
_parserBufferLock.wait()
|
||||||
|
defer { _parserBufferLock.signal() }
|
||||||
|
|
||||||
data.withMemoryRebound(to: UInt8.self, capacity: length) { (ptr) -> Void in
|
data.withMemoryRebound(to: UInt8.self, capacity: length) { (ptr) -> Void in
|
||||||
if var parserBuffer = parserBuffer {
|
if var parserBuffer = parserBuffer {
|
||||||
parserBuffer.append(ptr, count: length)
|
parserBuffer.append(ptr, count: length)
|
||||||
@@ -366,10 +404,10 @@ public class StreamingParser: HTTPResponseWriter {
|
|||||||
|
|
||||||
var header = "HTTP/1.1 \(status.code) \(status.reasonPhrase)\r\n"
|
var header = "HTTP/1.1 \(status.code) \(status.reasonPhrase)\r\n"
|
||||||
|
|
||||||
let isContinue = status == .continue
|
let isInformational = status.class == .informational
|
||||||
|
|
||||||
var headers = headers
|
var headers = headers
|
||||||
if !isContinue {
|
if !isInformational {
|
||||||
adjustHeaders(status: status, headers: &headers)
|
adjustHeaders(status: status, headers: &headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,12 +416,16 @@ public class StreamingParser: HTTPResponseWriter {
|
|||||||
header += "\(key): \(value)\r\n"
|
header += "\(key): \(value)\r\n"
|
||||||
}
|
}
|
||||||
header.append("\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
|
// FIXME headers are US-ASCII, anything else should be encoded using [RFC5987] some lines above
|
||||||
// TODO use requested encoding if specified
|
// TODO use requested encoding if specified
|
||||||
if let data = header.data(using: .utf8) {
|
if let data = header.data(using: .utf8) {
|
||||||
self.parserConnector?.queueSocketWrite(data, completion: completion)
|
self.parserConnector?.queueSocketWrite(data, completion: completion)
|
||||||
if !isContinue {
|
if !isInformational {
|
||||||
headersWritten = true
|
headersWritten = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -414,7 +456,6 @@ public class StreamingParser: HTTPResponseWriter {
|
|||||||
headers[.transferEncoding] = nil
|
headers[.transferEncoding] = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if clientRequestedKeepAlive {
|
if clientRequestedKeepAlive {
|
||||||
headers[.connection] = "Keep-Alive"
|
headers[.connection] = "Keep-Alive"
|
||||||
} else {
|
} else {
|
||||||
@@ -453,10 +494,19 @@ public class StreamingParser: HTTPResponseWriter {
|
|||||||
dataToWrite = data.withUnsafeBytes { Data($0) }
|
dataToWrite = data.withUnsafeBytes { Data($0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard !abortCalled else {
|
||||||
|
completion(.error(StreamingParserError.ConnectionAbortedError))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
self.parserConnector?.queueSocketWrite(dataToWrite, completion: completion)
|
self.parserConnector?.queueSocketWrite(dataToWrite, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func done(completion: @escaping (Result) -> Void) {
|
public func done(completion: @escaping (Result) -> Void) {
|
||||||
|
guard !abortCalled else {
|
||||||
|
completion(.error(StreamingParserError.ConnectionAbortedError))
|
||||||
|
return
|
||||||
|
}
|
||||||
if isChunked {
|
if isChunked {
|
||||||
let chunkTerminate = "0\r\n\r\n".data(using: .utf8)!
|
let chunkTerminate = "0\r\n\r\n".data(using: .utf8)!
|
||||||
self.parserConnector?.queueSocketWrite(chunkTerminate, completion: completion)
|
self.parserConnector?.queueSocketWrite(chunkTerminate, completion: completion)
|
||||||
@@ -466,7 +516,9 @@ public class StreamingParser: HTTPResponseWriter {
|
|||||||
self.parsedURL = nil
|
self.parsedURL = nil
|
||||||
self.parsedHeaders = HTTPHeaders()
|
self.parsedHeaders = HTTPHeaders()
|
||||||
self.lastHeaderName = nil
|
self.lastHeaderName = nil
|
||||||
|
_parserBufferLock.wait()
|
||||||
self.parserBuffer = nil
|
self.parserBuffer = nil
|
||||||
|
_parserBufferLock.signal()
|
||||||
self.parsedHTTPMethod = nil
|
self.parsedHTTPMethod = nil
|
||||||
self.parsedHTTPVersion = nil
|
self.parsedHTTPVersion = nil
|
||||||
self.lastCallBack = .idle
|
self.lastCallBack = .idle
|
||||||
@@ -479,15 +531,23 @@ public class StreamingParser: HTTPResponseWriter {
|
|||||||
// But since that block was removed, we're calling it directly
|
// But since that block was removed, we're calling it directly
|
||||||
if self.clientRequestedKeepAlive {
|
if self.clientRequestedKeepAlive {
|
||||||
self.keepAliveUntil = Date(timeIntervalSinceNow: keepAliveTimeout).timeIntervalSinceReferenceDate
|
self.keepAliveUntil = Date(timeIntervalSinceNow: keepAliveTimeout).timeIntervalSinceReferenceDate
|
||||||
|
guard !abortCalled else {
|
||||||
|
completion(.error(StreamingParserError.ConnectionAbortedError))
|
||||||
|
return
|
||||||
|
}
|
||||||
self.parserConnector?.responseComplete()
|
self.parserConnector?.responseComplete()
|
||||||
} else {
|
} else {
|
||||||
|
guard !abortCalled else {
|
||||||
|
completion(.error(StreamingParserError.ConnectionAbortedError))
|
||||||
|
return
|
||||||
|
}
|
||||||
self.parserConnector?.responseCompleteCloseWriter()
|
self.parserConnector?.responseCompleteCloseWriter()
|
||||||
}
|
}
|
||||||
completion(.ok)
|
completion(.ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func abort() {
|
public func abort() {
|
||||||
fatalError("abort called, not sure what to do with it")
|
abortCalled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ extension HTTPVersion : Hashable {
|
|||||||
public var hashValue: Int {
|
public var hashValue: Int {
|
||||||
return (major << 8) | minor
|
return (major << 8) | minor
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension HTTPVersion {
|
||||||
/// :nodoc:
|
/// :nodoc:
|
||||||
public static func == (lhs: HTTPVersion, rhs: HTTPVersion) -> Bool {
|
public static func == (lhs: HTTPVersion, rhs: HTTPVersion) -> Bool {
|
||||||
return lhs.major == rhs.major && lhs.minor == rhs.minor
|
return lhs.major == rhs.major && lhs.minor == rhs.minor
|
||||||
@@ -46,12 +48,11 @@ extension HTTPVersion : Comparable {
|
|||||||
return lhs.minor < rhs.minor
|
return lhs.minor < rhs.minor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension HTTPVersion: CustomStringConvertible {
|
extension HTTPVersion: CustomStringConvertible {
|
||||||
/// :nodoc:
|
/// :nodoc:
|
||||||
public var description: String {
|
public var description: String {
|
||||||
return "HTTP/" + major.description + "." + minor.description
|
return "HTTP/\(major).\(minor)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Dispatch
|
import Dispatch
|
||||||
|
import ServerSecurity
|
||||||
|
|
||||||
///:nodoc:
|
///:nodoc:
|
||||||
public enum PoCSocketError: Error {
|
public enum PoCSocketError: Error {
|
||||||
@@ -22,7 +23,7 @@ public enum PoCSocketError: Error {
|
|||||||
/// Intentionally a thin layer over `recv(2)`/`send(2)` so uses the same argument types.
|
/// 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.
|
/// 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.
|
/// This is because we expect the caller might need functionality we haven't implemented here.
|
||||||
internal class PoCSocket {
|
internal class PoCSocket: ConnectionDelegate {
|
||||||
|
|
||||||
/// hold the file descriptor for the socket supplied by the OS. `-1` is invalid socket
|
/// hold the file descriptor for the socket supplied by the OS. `-1` is invalid socket
|
||||||
internal var socketfd: Int32 = -1
|
internal var socketfd: Int32 = -1
|
||||||
@@ -31,10 +32,44 @@ internal class PoCSocket {
|
|||||||
internal var listeningPort: Int32 = -1
|
internal var listeningPort: Int32 = -1
|
||||||
|
|
||||||
/// Track state between `listen(2)` and `shutdown(2)`
|
/// 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)`
|
/// 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
|
/// track whether a shutdown is in progress so we can suppress error messages
|
||||||
private let _isShuttingDownLock = DispatchSemaphore(value: 1)
|
private let _isShuttingDownLock = DispatchSemaphore(value: 1)
|
||||||
@@ -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
|
/// Call recv(2) with buffer allocated by our caller and return the output
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
@@ -80,7 +144,14 @@ internal class PoCSocket {
|
|||||||
//Make sure data isn't re-used
|
//Make sure data isn't re-used
|
||||||
readBuffer.initialize(to: 0x0, count: maxLength)
|
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
|
//Leave this as a local variable to facilitate Setting a Watchpoint in lldb
|
||||||
return read
|
return read
|
||||||
}
|
}
|
||||||
@@ -106,7 +177,14 @@ internal class PoCSocket {
|
|||||||
throw PoCSocketError.InvalidBufferError
|
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
|
//Leave this as a local variable to facilitate Setting a Watchpoint in lldb
|
||||||
return sent
|
return sent
|
||||||
}
|
}
|
||||||
@@ -114,18 +192,24 @@ internal class PoCSocket {
|
|||||||
/// Calls `shutdown(2)` and `close(2)` on a socket
|
/// Calls `shutdown(2)` and `close(2)` on a socket
|
||||||
internal func shutdownAndClose() {
|
internal func shutdownAndClose() {
|
||||||
self.isShuttingDown = true
|
self.isShuttingDown = true
|
||||||
|
if let tls = self.TLSdelegate {
|
||||||
|
tls.willDestroy()
|
||||||
|
}
|
||||||
if socketfd < 1 {
|
if socketfd < 1 {
|
||||||
//Nothing to do. Maybe it was closed already
|
//Nothing to do. Maybe it was closed already
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//print("Shutting down socket \(self.socketfd)")
|
if hasClosed {
|
||||||
|
//Nothing to do. It was closed already
|
||||||
|
return
|
||||||
|
}
|
||||||
if self.isListening || self.isConnected {
|
if self.isListening || self.isConnected {
|
||||||
//print("Shutting down socket")
|
|
||||||
_ = shutdown(self.socketfd, Int32(SHUT_RDWR))
|
_ = shutdown(self.socketfd, Int32(SHUT_RDWR))
|
||||||
self.isListening = false
|
self.isListening = false
|
||||||
}
|
}
|
||||||
self.isConnected = false
|
self.isConnected = false
|
||||||
close(self.socketfd)
|
close(self.socketfd)
|
||||||
|
self.hasClosed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Thin wrapper around `accept(2)`
|
/// Thin wrapper around `accept(2)`
|
||||||
@@ -167,6 +251,11 @@ internal class PoCSocket {
|
|||||||
retVal.isConnected = true
|
retVal.isConnected = true
|
||||||
retVal.socketfd = acceptFD
|
retVal.socketfd = acceptFD
|
||||||
|
|
||||||
|
// TLS delegate does post accept handling and verification
|
||||||
|
if let tls = self.TLSdelegate {
|
||||||
|
try tls.didAccept(connection: retVal)
|
||||||
|
}
|
||||||
|
|
||||||
return retVal
|
return retVal
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,6 +274,11 @@ internal class PoCSocket {
|
|||||||
|
|
||||||
if socketfd <= 0 {
|
if socketfd <= 0 {
|
||||||
throw PoCSocketError.InvalidSocketError
|
throw PoCSocketError.InvalidSocketError
|
||||||
|
|
||||||
|
}
|
||||||
|
// Initialize delegate
|
||||||
|
if let tls = self.TLSdelegate {
|
||||||
|
try tls.didCreateServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
var on: Int32 = 1
|
var on: Int32 = 1
|
||||||
@@ -213,18 +307,14 @@ internal class PoCSocket {
|
|||||||
sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
|
sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
let _ = withUnsafePointer(to: &addr) {
|
_ = withUnsafePointer(to: &addr) {
|
||||||
bind(self.socketfd, UnsafePointer<sockaddr>(OpaquePointer($0)), socklen_t(MemoryLayout<sockaddr_in>.size))
|
bind(self.socketfd, UnsafePointer<sockaddr>(OpaquePointer($0)), socklen_t(MemoryLayout<sockaddr_in>.size))
|
||||||
}
|
}
|
||||||
|
|
||||||
//print("bindResult is \(bindResult)")
|
_ = listen(self.socketfd, maxBacklogSize)
|
||||||
|
|
||||||
let _ = listen(self.socketfd, maxBacklogSize)
|
|
||||||
|
|
||||||
isListening = true
|
isListening = true
|
||||||
|
|
||||||
//print("listenResult is \(listenResult)")
|
|
||||||
|
|
||||||
var addr_in = sockaddr_in()
|
var addr_in = sockaddr_in()
|
||||||
|
|
||||||
listeningPort = try withUnsafePointer(to: &addr_in) { pointer in
|
listeningPort = try withUnsafePointer(to: &addr_in) { pointer in
|
||||||
@@ -238,8 +328,6 @@ internal class PoCSocket {
|
|||||||
return Int32(Int(OSHostByteOrder()) != OSLittleEndian ? addr_in.sin_port.littleEndian : addr_in.sin_port.bigEndian)
|
return Int32(Int(OSHostByteOrder()) != OSLittleEndian ? addr_in.sin_port.littleEndian : addr_in.sin_port.bigEndian)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
//print("listeningPort is \(listeningPort)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check to see if socket is being used
|
/// Check to see if socket is being used
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Dispatch
|
import Dispatch
|
||||||
|
import ServerSecurity
|
||||||
|
|
||||||
///:nodoc:
|
///:nodoc:
|
||||||
public class PoCSocketConnectionListener: ParserConnecting {
|
public class PoCSocketConnectionListener: ParserConnecting {
|
||||||
@@ -20,7 +21,25 @@ public class PoCSocketConnectionListener: ParserConnecting {
|
|||||||
|
|
||||||
/// 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 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
|
/// Queues for managing access to the socket without blocking the world
|
||||||
let socketReaderQueue: DispatchQueue
|
let socketReaderQueue: DispatchQueue
|
||||||
@@ -69,8 +88,28 @@ public class PoCSocketConnectionListener: ParserConnecting {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///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
|
/// Largest number of bytes we're willing to allocate for a Read
|
||||||
// it's an anti-heartbleed-type paranoia check
|
/// it's an anti-heartbleed-type paranoia check
|
||||||
private var maxReadLength: Int = 1048576
|
private var maxReadLength: Int = 1048576
|
||||||
|
|
||||||
/// initializer
|
/// initializer
|
||||||
@@ -92,10 +131,7 @@ public class PoCSocketConnectionListener: ParserConnecting {
|
|||||||
|
|
||||||
/// Check if socket is still open. Used to decide whether it should be closed/pruned after timeout
|
/// Check if socket is still open. Used to decide whether it should be closed/pruned after timeout
|
||||||
public var isOpen: Bool {
|
public var isOpen: Bool {
|
||||||
guard let socket = self.socket else {
|
return self.socket?.isOpen() ?? false
|
||||||
return false
|
|
||||||
}
|
|
||||||
return socket.isOpen()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Close the socket and free up memory unless we're in the middle of a request
|
/// Close the socket and free up memory unless we're in the middle of a request
|
||||||
@@ -151,6 +187,10 @@ public class PoCSocketConnectionListener: ParserConnecting {
|
|||||||
|
|
||||||
/// Check if the socket is idle, and if so, call close()
|
/// Check if the socket is idle, and if so, call close()
|
||||||
func closeIfIdleSocket() {
|
func closeIfIdleSocket() {
|
||||||
|
if !self.responseCompleted {
|
||||||
|
// We're in the middle of a connection - we're not idle
|
||||||
|
return
|
||||||
|
}
|
||||||
let now = Date().timeIntervalSinceReferenceDate
|
let now = Date().timeIntervalSinceReferenceDate
|
||||||
if let keepAliveUntil = parser?.keepAliveUntil, now >= keepAliveUntil {
|
if let keepAliveUntil = parser?.keepAliveUntil, now >= keepAliveUntil {
|
||||||
print("Closing idle socket \(socketFD)")
|
print("Closing idle socket \(socketFD)")
|
||||||
@@ -159,26 +199,33 @@ public class PoCSocketConnectionListener: ParserConnecting {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func cleanup() {
|
func cleanup() {
|
||||||
self.readerSource?.setEventHandler(handler: nil)
|
guard !cleanupCalled else {
|
||||||
self.readerSource?.setCancelHandler(handler: nil)
|
// This prevents a rare crash (~1 in 300,000) where cleanup is called from both reader and writer
|
||||||
|
// queues simultaneously
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
self.readerSource = nil
|
//allow for memory to be reclaimed
|
||||||
self.socket = nil
|
if let strongReaderSource = self.readerSource {
|
||||||
self.parser?.parserConnector = nil //allows for memory to be reclaimed
|
strongReaderSource.setEventHandler(handler: nil)
|
||||||
self.parser = 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
|
/// Called by the parser to let us know that a response has started being created
|
||||||
public func responseBeginning() {
|
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
|
/// Called by the parser to let us know that a response is complete, and we can close after timeout
|
||||||
public func responseComplete() {
|
public func responseComplete() {
|
||||||
|
self.responseCompleted = true
|
||||||
self.socketWriterQueue.async { [weak self] in
|
self.socketWriterQueue.async { [weak self] in
|
||||||
self?.responseCompleted = true
|
|
||||||
if self?.readerSource?.isCancelled ?? true {
|
if self?.readerSource?.isCancelled ?? true {
|
||||||
self?.close()
|
self?.close()
|
||||||
}
|
}
|
||||||
@@ -187,18 +234,32 @@ public class PoCSocketConnectionListener: ParserConnecting {
|
|||||||
|
|
||||||
/// Called by the parser to let us know that a response is complete and we should close the socket
|
/// Called by the parser to let us know that a response is complete and we should close the socket
|
||||||
public func responseCompleteCloseWriter() {
|
public func responseCompleteCloseWriter() {
|
||||||
|
self.responseCompleted = true
|
||||||
self.socketWriterQueue.async { [weak self] in
|
self.socketWriterQueue.async { [weak self] in
|
||||||
self?.responseCompleted = true
|
|
||||||
self?.close()
|
self?.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts reading from the socket and feeding that data to the parser
|
/// Starts reading from the socket and feeding that data to the parser
|
||||||
public func process() {
|
public func process() {
|
||||||
try! socket?.setBlocking(mode: true)
|
let tempReaderSource: DispatchSourceRead
|
||||||
|
// Make sure we have a socket here. Don't use guard so that
|
||||||
let tempReaderSource = DispatchSource.makeReadSource(fileDescriptor: socket?.socketfd ?? -1,
|
// 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)
|
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
|
tempReaderSource.setEventHandler { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
@@ -215,22 +276,30 @@ public class PoCSocketConnectionListener: ParserConnecting {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let strongSocket = strongSelf.socket {
|
||||||
var length = 1 //initial value
|
var length = 1 //initial value
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if strongSelf.socket?.socketfd ?? -1 > 0 {
|
if strongSocket.socketfd > 0 {
|
||||||
var maxLength: Int = Int(strongSelf.readerSource?.data ?? 0)
|
var maxLength: Int = Int(strongSelf.readerSource?.data ?? 0)
|
||||||
if (maxLength > strongSelf.maxReadLength) || (maxLength <= 0) {
|
if (maxLength > strongSelf.maxReadLength) || (maxLength <= 0) {
|
||||||
maxLength = strongSelf.maxReadLength
|
maxLength = strongSelf.maxReadLength
|
||||||
}
|
}
|
||||||
|
// 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)
|
var readBuffer: UnsafeMutablePointer<Int8> = UnsafeMutablePointer<Int8>.allocate(capacity: maxLength)
|
||||||
length = try strongSelf.socket?.socketRead(into: &readBuffer, maxLength:maxLength) ?? -1
|
length = try strongSocket.socketRead(into: &readBuffer, maxLength:maxLength)
|
||||||
if length > 0 {
|
if length > 0 {
|
||||||
self?.responseCompleted = false
|
strongSelf.responseCompleted = false
|
||||||
|
|
||||||
let data = Data(bytes: readBuffer, count: length)
|
let data = Data(bytes: readBuffer, count: length)
|
||||||
let numberParsed = strongSelf.parser?.readStream(data:data) ?? 0
|
let numberParsed = strongSelf.parser?.readStream(data:data) ?? 0
|
||||||
|
|
||||||
|
defer {
|
||||||
|
readBuffer.deallocate(capacity: maxLength)
|
||||||
|
}
|
||||||
|
|
||||||
if numberParsed != data.count {
|
if numberParsed != data.count {
|
||||||
print("Error: wrong number of bytes consumed by parser (\(numberParsed) instead of \(data.count)")
|
print("Error: wrong number of bytes consumed by parser (\(numberParsed) instead of \(data.count)")
|
||||||
}
|
}
|
||||||
@@ -240,26 +309,38 @@ public class PoCSocketConnectionListener: ParserConnecting {
|
|||||||
length = -1
|
length = -1
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
print("ReaderSource Event Error: \(error)")
|
//print("ReaderSource Event Error: \(error)")
|
||||||
self?.readerSource?.cancel()
|
strongSelf.readerSource?.cancel()
|
||||||
self?.errorOccurred = true
|
|
||||||
self?.close()
|
strongSelf.errorOccurred = true
|
||||||
|
strongSelf.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
if length == 0 {
|
if length == 0 {
|
||||||
self?.readerSource?.cancel()
|
//print("ReaderSource Read count zero. Cancelling.")
|
||||||
|
strongSelf.readerSource?.cancel()
|
||||||
}
|
}
|
||||||
if length < 0 {
|
if length < 0 {
|
||||||
self?.errorOccurred = true
|
//print("ReaderSource Read count negative. Closing.")
|
||||||
self?.readerSource?.cancel()
|
strongSelf.errorOccurred = true
|
||||||
self?.close()
|
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
|
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()
|
self.readerSource?.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,14 +364,19 @@ public class PoCSocketConnectionListener: ParserConnecting {
|
|||||||
|
|
||||||
while written < data.count && !errorOccurred {
|
while written < data.count && !errorOccurred {
|
||||||
try data.withUnsafeBytes { (ptr: UnsafePointer<UInt8>) in
|
try data.withUnsafeBytes { (ptr: UnsafePointer<UInt8>) in
|
||||||
let result = try socket?.socketWrite(from: ptr + offset, bufSize:
|
if let strongSocket = socket {
|
||||||
data.count - offset) ?? -1
|
let result = try strongSocket.socketWrite(from: ptr + offset, bufSize:
|
||||||
|
data.count - offset)
|
||||||
if result < 0 {
|
if result < 0 {
|
||||||
print("Received broken write socket indication")
|
//print("Received broken write socket indication")
|
||||||
errorOccurred = true
|
errorOccurred = true
|
||||||
} else {
|
} else {
|
||||||
written += result
|
written += result
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
//print("Socket unexpectedly nil during write")
|
||||||
|
errorOccurred = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
offset = data.count - written
|
offset = data.count - written
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
|
|
||||||
import Dispatch
|
import Dispatch
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import ServerSecurity
|
||||||
|
import TLSService
|
||||||
|
|
||||||
// MARK: Server
|
// MARK: Server
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ public class PoCSocketSimpleServer: CurrentConnectionCounting {
|
|||||||
/// Collection of listeners of sockets. Used to kill connections on timeout or shutdown
|
/// Collection of listeners of sockets. Used to kill connections on timeout or shutdown
|
||||||
private var connectionListenerList = ConnectionListenerCollection()
|
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"))
|
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
|
/// The port we're listening on. Used primarily to query a randomly assigned port during XCTests
|
||||||
@@ -66,10 +67,11 @@ public class PoCSocketSimpleServer: CurrentConnectionCounting {
|
|||||||
acceptCount: Int = 0,
|
acceptCount: Int = 0,
|
||||||
maxReadLength: Int = 1048576,
|
maxReadLength: Int = 1048576,
|
||||||
keepAliveTimeout: Double = 5.0,
|
keepAliveTimeout: Double = 5.0,
|
||||||
|
tls: TLSConfiguration? = nil,
|
||||||
handler: @escaping HTTPRequestHandler) throws {
|
handler: @escaping HTTPRequestHandler) throws {
|
||||||
|
|
||||||
// Don't let a signal generated by a broken socket kill the server
|
// Don't let a signal generated by a broken socket kill the server
|
||||||
signal(SIGPIPE, SIG_IGN);
|
signal(SIGPIPE, SIG_IGN)
|
||||||
|
|
||||||
if queueCount > 0 {
|
if queueCount > 0 {
|
||||||
queueMax = queueCount
|
queueMax = queueCount
|
||||||
@@ -77,6 +79,11 @@ public class PoCSocketSimpleServer: CurrentConnectionCounting {
|
|||||||
if acceptCount > 0 {
|
if acceptCount > 0 {
|
||||||
acceptMax = acceptCount
|
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)
|
try self.serverSocket.bindAndListen(on: port)
|
||||||
|
|
||||||
pruneSocketTimer.setEventHandler { [weak self] in
|
pruneSocketTimer.setEventHandler { [weak self] 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
|
/// Storage for weak connection listeners
|
||||||
var storage = [WeakConnectionListener<PoCSocketConnectionListener>]()
|
private var storage = [WeakConnectionListener<PoCSocketConnectionListener>]()
|
||||||
|
|
||||||
/// Add a new connection to the collection
|
/// Add a new connection to the collection
|
||||||
///
|
///
|
||||||
@@ -173,7 +181,7 @@ class ConnectionListenerCollection {
|
|||||||
/// Used when shutting down the server to close all connections
|
/// Used when shutting down the server to close all connections
|
||||||
func closeAll() {
|
func closeAll() {
|
||||||
lock.wait()
|
lock.wait()
|
||||||
storage.filter { nil != $0.value }.forEach { $0.value?.close() }
|
storage.filter { $0.value != nil }.forEach { $0.value?.close() }
|
||||||
lock.signal()
|
lock.signal()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,16 +189,15 @@ class ConnectionListenerCollection {
|
|||||||
func prune() {
|
func prune() {
|
||||||
lock.wait()
|
lock.wait()
|
||||||
storage.filter { nil != $0.value }.forEach { $0.value?.closeIfIdleSocket() }
|
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()
|
lock.signal()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Count of collections
|
/// Count of collections
|
||||||
var count: Int {
|
var count: Int {
|
||||||
lock.wait()
|
lock.wait()
|
||||||
let count = storage.filter { nil != $0.value }.count
|
let count = storage.filter { $0.value != nil }.count
|
||||||
lock.signal()
|
lock.signal()
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,482 +59,10 @@ class ServerTests: XCTestCase {
|
|||||||
XCTAssertEqual("Hello, World!", resolver.responseBody?.withUnsafeBytes { String(bytes: $0, encoding: .utf8) } ?? "Nil")
|
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 = [
|
static var allTests = [
|
||||||
("testEcho", testEcho),
|
("testEcho", testEcho),
|
||||||
("testHello", testHello),
|
("testHello", testHello),
|
||||||
("testSimpleHello", testSimpleHello),
|
("testSimpleHello", testSimpleHello),
|
||||||
("testResponseOK", testResponseOK),
|
("testResponseOK", testResponseOK),
|
||||||
("testOkEndToEnd", testOkEndToEnd),
|
|
||||||
("testHelloEndToEnd", testHelloEndToEnd),
|
|
||||||
("testSimpleHelloEndToEnd", testSimpleHelloEndToEnd),
|
|
||||||
("testRequestEchoEndToEnd", testRequestEchoEndToEnd),
|
|
||||||
("testRequestKeepAliveEchoEndToEnd", testRequestKeepAliveEchoEndToEnd),
|
|
||||||
("testRequestLargeEchoEndToEnd", testRequestLargeEchoEndToEnd),
|
|
||||||
("testExplicitCloseConnections", testExplicitCloseConnections),
|
|
||||||
("testRequestLargePostHelloWorld", testRequestLargePostHelloWorld),
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -62,9 +62,28 @@ class VersionTests: XCTestCase {
|
|||||||
XCTAssertFalse(version20 <= version11)
|
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 = [
|
static var allTests = [
|
||||||
("testEquals", testEquals),
|
("testEquals", testEquals),
|
||||||
("testGreater", testGreater),
|
("testGreater", testGreater),
|
||||||
("testLess", testLess),
|
("testLess", testLess),
|
||||||
|
("testHashValue", testHashValue),
|
||||||
|
("testDescription", testDescription)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,4 +15,6 @@ XCTMain([
|
|||||||
testCase(HeadersTests.allTests),
|
testCase(HeadersTests.allTests),
|
||||||
testCase(ResponseTests.allTests),
|
testCase(ResponseTests.allTests),
|
||||||
testCase(ServerTests.allTests),
|
testCase(ServerTests.allTests),
|
||||||
|
testCase(ServerTestsEndToEnd.allTests),
|
||||||
|
testCase(TLSServerTests.allTests),
|
||||||
])
|
])
|
||||||
|
|||||||
Reference in New Issue
Block a user