Add https support
This commit is contained in:
parent
f8e0cc44f9
commit
0727ced422
17 changed files with 383 additions and 83 deletions
45
README.md
45
README.md
|
@ -64,6 +64,21 @@ testRest {
|
||||||
path='testEndpoint'
|
path='testEndpoint'
|
||||||
name='testRest'
|
name='testRest'
|
||||||
}
|
}
|
||||||
|
testHttps {
|
||||||
|
soap=false
|
||||||
|
port=10443
|
||||||
|
path='testHttps'
|
||||||
|
name='testHttps'
|
||||||
|
method='GET'
|
||||||
|
https={
|
||||||
|
keystorePath='/tmp/keystore.jks'
|
||||||
|
keystorePassword='keystorePass'
|
||||||
|
keyPassword='keyPass'
|
||||||
|
truststorePath='/tmp/truststore.jks'
|
||||||
|
truststorePassword='truststorePass'
|
||||||
|
requireClientAuth=true
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Build with docker
|
### Build with docker
|
||||||
|
@ -96,7 +111,15 @@ remoteMockServer.addMock(new AddMock(
|
||||||
statusCode: ...,
|
statusCode: ...,
|
||||||
method: ...,
|
method: ...,
|
||||||
responseHeaders: ...,
|
responseHeaders: ...,
|
||||||
schema: ...
|
schema: ...,
|
||||||
|
https: new Https(
|
||||||
|
keystorePath: '/tmp/keystore.jks',
|
||||||
|
keystorePassword: 'keystorePass',
|
||||||
|
keyPassword: 'keyPass',
|
||||||
|
truststorePath: '/tmp/truststore.jks',
|
||||||
|
truststorePassword: 'truststorePass',
|
||||||
|
requireClientAuth: true
|
||||||
|
)
|
||||||
))
|
))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -117,6 +140,14 @@ Send POST request to localhost:<PORT>/serverControl
|
||||||
<responseHeaders>...</responseHeaders>
|
<responseHeaders>...</responseHeaders>
|
||||||
<schema>...</schema>
|
<schema>...</schema>
|
||||||
<imports alias="..." fullClassName="..."/>
|
<imports alias="..." fullClassName="..."/>
|
||||||
|
<https>
|
||||||
|
<keystorePath>/tmp/keystore.jks</keystorePath>
|
||||||
|
<keystorePassword>keystorePass</keystorePassword>
|
||||||
|
<keyPassword>keyPass</keyPassword>
|
||||||
|
<truststorePath>/tmp/truststore.jks</truststorePath>
|
||||||
|
<truststorePassword>truststorePass</truststorePassword>
|
||||||
|
<requireClientAuth>true</requireClientAuth>
|
||||||
|
</https>
|
||||||
</addMock>
|
</addMock>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -133,6 +164,18 @@ Send POST request to localhost:<PORT>/serverControl
|
||||||
- responseHeaders - groovyClosure as string which must evaluate to Map which will be added to response headers, default { _ -> \[:] }
|
- responseHeaders - groovyClosure as string which must evaluate to Map which will be added to response headers, default { _ -> \[:] }
|
||||||
- schema - path to xsd schema file on mockserver classpath; default empty, so no vallidation of request is performed; if validation fails then response has got status 400 and response is raw message from validator
|
- schema - path to xsd schema file on mockserver classpath; default empty, so no vallidation of request is performed; if validation fails then response has got status 400 and response is raw message from validator
|
||||||
- imports - list of imports for closures (each import is separate tag); `alias` is the name of `fullClassName` available in closure; `fullClassName` must be available on classpath of mock server
|
- imports - list of imports for closures (each import is separate tag); `alias` is the name of `fullClassName` available in closure; `fullClassName` must be available on classpath of mock server
|
||||||
|
- https - HTTPS configuration
|
||||||
|
|
||||||
|
#### HTTPS configuration
|
||||||
|
|
||||||
|
- keystorePath - path to keystore in JKS format, keystore should contains only one privateKeyEntry
|
||||||
|
- keystorePassword - keystore password
|
||||||
|
- keyPassword - key password
|
||||||
|
- truststorePath - path to truststore in JKS format
|
||||||
|
- truststorePassword - truststore password
|
||||||
|
- requireClientAuth - whether client auth is required (two-way SSL)
|
||||||
|
|
||||||
|
**HTTP** and **HTTPS** should be started on separated ports.
|
||||||
|
|
||||||
### Closures request properties
|
### Closures request properties
|
||||||
|
|
||||||
|
|
|
@ -18,5 +18,16 @@
|
||||||
<xs:attribute name="alias" type="xs:string"/>
|
<xs:attribute name="alias" type="xs:string"/>
|
||||||
<xs:attribute name="fullClassName" type="xs:string"/>
|
<xs:attribute name="fullClassName" type="xs:string"/>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
|
<xs:complexType name="https">
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element name="keystorePath" type="xs:string" />
|
||||||
|
<xs:element name="keystorePassword" type="xs:string" />
|
||||||
|
<xs:element name="keyPassword" type="xs:string" />
|
||||||
|
<xs:element name="truststorePath" type="xs:string" />
|
||||||
|
<xs:element name="truststorePassword" type="xs:string" />
|
||||||
|
<xs:element name="requireClientAuth" type="xs:boolean" />
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
</xs:schema>
|
</xs:schema>
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
<xs:element name="soap" type="xs:boolean" minOccurs="0"/>
|
<xs:element name="soap" type="xs:boolean" minOccurs="0"/>
|
||||||
<xs:element name="statusCode" type="xs:int" minOccurs="0"/>
|
<xs:element name="statusCode" type="xs:int" minOccurs="0"/>
|
||||||
<xs:element name="method" type="common:method" minOccurs="0"/>
|
<xs:element name="method" type="common:method" minOccurs="0"/>
|
||||||
|
<xs:element name="https" type="common:https" minOccurs="0" />
|
||||||
<xs:element name="responseHeaders" type="xs:string" minOccurs="0"/>
|
<xs:element name="responseHeaders" type="xs:string" minOccurs="0"/>
|
||||||
<xs:element name="schema" type="xs:string" minOccurs="0"/>
|
<xs:element name="schema" type="xs:string" minOccurs="0"/>
|
||||||
<xs:element name="imports" type="common:importAlias" minOccurs="0" maxOccurs="unbounded"/>
|
<xs:element name="imports" type="common:importAlias" minOccurs="0" maxOccurs="unbounded"/>
|
||||||
|
|
|
@ -44,6 +44,10 @@
|
||||||
<artifactId>mockserver-client</artifactId>
|
<artifactId>mockserver-client</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>info.solidsoft.spock</groupId>
|
||||||
|
<artifactId>spock-global-unroll</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -0,0 +1,152 @@
|
||||||
|
package pl.touk.mockserver.tests
|
||||||
|
|
||||||
|
import groovy.util.slurpersupport.GPathResult
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse
|
||||||
|
import org.apache.http.client.methods.HttpPost
|
||||||
|
import org.apache.http.conn.ssl.SSLConnectionSocketFactory
|
||||||
|
import org.apache.http.conn.ssl.SSLContexts
|
||||||
|
import org.apache.http.entity.ContentType
|
||||||
|
import org.apache.http.entity.StringEntity
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient
|
||||||
|
import org.apache.http.impl.client.HttpClients
|
||||||
|
import pl.touk.mockserver.api.common.Https
|
||||||
|
import pl.touk.mockserver.api.request.AddMock
|
||||||
|
import pl.touk.mockserver.client.RemoteMockServer
|
||||||
|
import pl.touk.mockserver.client.Util
|
||||||
|
import pl.touk.mockserver.server.HttpMockServer
|
||||||
|
import spock.lang.AutoCleanup
|
||||||
|
import spock.lang.Shared
|
||||||
|
import spock.lang.Specification
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext
|
||||||
|
import javax.net.ssl.SSLHandshakeException
|
||||||
|
import java.security.KeyStore
|
||||||
|
|
||||||
|
class MockServerHttpsTest extends Specification {
|
||||||
|
|
||||||
|
RemoteMockServer remoteMockServer = new RemoteMockServer('localhost', 19000)
|
||||||
|
|
||||||
|
@AutoCleanup('stop')
|
||||||
|
HttpMockServer httpMockServer = new HttpMockServer(19000)
|
||||||
|
|
||||||
|
@Shared
|
||||||
|
SSLContext noClientAuthSslContext = SSLContexts.custom()
|
||||||
|
.loadTrustMaterial(trustStore())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
@Shared
|
||||||
|
SSLContext trustedCertificateSslContext = SSLContexts.custom()
|
||||||
|
.loadKeyMaterial(trustedCertificateKeystore(), 'changeit'.toCharArray())
|
||||||
|
.loadTrustMaterial(trustStore())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
@Shared
|
||||||
|
SSLContext untrustedCertificateSslContext = SSLContexts.custom()
|
||||||
|
.loadKeyMaterial(untrustedCertificateKeystore(), 'changeit'.toCharArray())
|
||||||
|
.loadTrustMaterial(trustStore())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
def 'should handle HTTPS server' () {
|
||||||
|
given:
|
||||||
|
remoteMockServer.addMock(new AddMock(
|
||||||
|
name: 'testHttps',
|
||||||
|
path: 'testEndpoint',
|
||||||
|
port: 10443,
|
||||||
|
predicate: '''{req -> req.xml.name() == 'request'}''',
|
||||||
|
response: '''{req -> "<goodResponse-${req.xml.name()}/>"}''',
|
||||||
|
https: new Https(
|
||||||
|
keyPassword: 'changeit',
|
||||||
|
keystorePassword: 'changeit',
|
||||||
|
keystorePath: MockServerHttpsTest.classLoader.getResource('keystore.jks').path
|
||||||
|
),
|
||||||
|
soap: false
|
||||||
|
))
|
||||||
|
when:
|
||||||
|
HttpPost restPost = new HttpPost('https://localhost:10443/testEndpoint')
|
||||||
|
restPost.entity = new StringEntity('<request/>', ContentType.create("text/xml", "UTF-8"))
|
||||||
|
CloseableHttpResponse response = client(noClientAuthSslContext).execute(restPost)
|
||||||
|
then:
|
||||||
|
GPathResult restPostResponse = Util.extractXmlResponse(response)
|
||||||
|
restPostResponse.name() == 'goodResponse-request'
|
||||||
|
}
|
||||||
|
|
||||||
|
def 'should handle HTTPS server with client auth' () {
|
||||||
|
given:
|
||||||
|
remoteMockServer.addMock(new AddMock(
|
||||||
|
name: 'testHttps',
|
||||||
|
path: 'testEndpoint',
|
||||||
|
port: 10443,
|
||||||
|
predicate: '''{req -> req.xml.name() == 'request'}''',
|
||||||
|
response: '''{req -> "<goodResponse-${req.xml.name()}/>"}''',
|
||||||
|
https: new Https(
|
||||||
|
keyPassword: 'changeit',
|
||||||
|
keystorePassword: 'changeit',
|
||||||
|
keystorePath: MockServerHttpsTest.classLoader.getResource('keystore.jks').path,
|
||||||
|
truststorePath: MockServerHttpsTest.classLoader.getResource('truststore.jks').path,
|
||||||
|
truststorePassword: 'changeit',
|
||||||
|
requireClientAuth: true
|
||||||
|
),
|
||||||
|
soap: false
|
||||||
|
))
|
||||||
|
when:
|
||||||
|
HttpPost restPost = new HttpPost('https://localhost:10443/testEndpoint')
|
||||||
|
restPost.entity = new StringEntity('<request/>', ContentType.create("text/xml", "UTF-8"))
|
||||||
|
CloseableHttpResponse response = client(trustedCertificateSslContext).execute(restPost)
|
||||||
|
then:
|
||||||
|
GPathResult restPostResponse = Util.extractXmlResponse(response)
|
||||||
|
restPostResponse.name() == 'goodResponse-request'
|
||||||
|
}
|
||||||
|
|
||||||
|
def 'should handle HTTPS server with wrong client auth' () {
|
||||||
|
given:
|
||||||
|
remoteMockServer.addMock(new AddMock(
|
||||||
|
name: 'testHttps',
|
||||||
|
path: 'testEndpoint',
|
||||||
|
port: 10443,
|
||||||
|
predicate: '''{req -> req.xml.name() == 'request'}''',
|
||||||
|
response: '''{req -> "<goodResponse-${req.xml.name()}/>"}''',
|
||||||
|
https: new Https(
|
||||||
|
keyPassword: 'changeit',
|
||||||
|
keystorePassword: 'changeit',
|
||||||
|
keystorePath: MockServerHttpsTest.classLoader.getResource('keystore.jks').path,
|
||||||
|
truststorePath: MockServerHttpsTest.classLoader.getResource('truststore.jks').path,
|
||||||
|
truststorePassword: 'changeit',
|
||||||
|
requireClientAuth: true
|
||||||
|
),
|
||||||
|
soap: false
|
||||||
|
))
|
||||||
|
when:
|
||||||
|
HttpPost restPost = new HttpPost('https://localhost:10443/testEndpoint')
|
||||||
|
restPost.entity = new StringEntity('<request/>', ContentType.create("text/xml", "UTF-8"))
|
||||||
|
client(sslContext).execute(restPost)
|
||||||
|
then:
|
||||||
|
thrown(SSLHandshakeException)
|
||||||
|
where:
|
||||||
|
sslContext << [noClientAuthSslContext, untrustedCertificateSslContext]
|
||||||
|
}
|
||||||
|
|
||||||
|
private CloseableHttpClient client(SSLContext sslContext) {
|
||||||
|
return HttpClients.custom()
|
||||||
|
.setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)
|
||||||
|
.setSslcontext(sslContext)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyStore trustedCertificateKeystore() {
|
||||||
|
return loadKeystore('trusted.jks')
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyStore untrustedCertificateKeystore() {
|
||||||
|
return loadKeystore('untrusted.jks')
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyStore trustStore() {
|
||||||
|
return loadKeystore('truststore.jks')
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyStore loadKeystore(String fileName) {
|
||||||
|
KeyStore truststore = KeyStore.getInstance(KeyStore.defaultType)
|
||||||
|
truststore.load(new FileInputStream(MockServerHttpsTest.classLoader.getResource(fileName).path), "changeit".toCharArray());
|
||||||
|
return truststore
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,14 +27,15 @@ import pl.touk.mockserver.client.MockDoesNotExist
|
||||||
import pl.touk.mockserver.client.RemoteMockServer
|
import pl.touk.mockserver.client.RemoteMockServer
|
||||||
import pl.touk.mockserver.client.Util
|
import pl.touk.mockserver.client.Util
|
||||||
import pl.touk.mockserver.server.HttpMockServer
|
import pl.touk.mockserver.server.HttpMockServer
|
||||||
|
import spock.lang.AutoCleanup
|
||||||
import spock.lang.Shared
|
import spock.lang.Shared
|
||||||
import spock.lang.Specification
|
import spock.lang.Specification
|
||||||
import spock.lang.Unroll
|
|
||||||
|
|
||||||
class MockServerIntegrationTest extends Specification {
|
class MockServerIntegrationTest extends Specification {
|
||||||
|
|
||||||
RemoteMockServer remoteMockServer
|
RemoteMockServer remoteMockServer
|
||||||
|
|
||||||
|
@AutoCleanup('stop')
|
||||||
HttpMockServer httpMockServer
|
HttpMockServer httpMockServer
|
||||||
|
|
||||||
@Shared
|
@Shared
|
||||||
|
@ -45,10 +46,6 @@ class MockServerIntegrationTest extends Specification {
|
||||||
remoteMockServer = new RemoteMockServer('localhost', 9000)
|
remoteMockServer = new RemoteMockServer('localhost', 9000)
|
||||||
}
|
}
|
||||||
|
|
||||||
def cleanup() {
|
|
||||||
httpMockServer.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
def "should add working rest mock on endpoint"() {
|
def "should add working rest mock on endpoint"() {
|
||||||
expect:
|
expect:
|
||||||
remoteMockServer.addMock(new AddMock(
|
remoteMockServer.addMock(new AddMock(
|
||||||
|
@ -244,7 +241,6 @@ class MockServerIntegrationTest extends Specification {
|
||||||
soapPostResponse.Body.'goodResponseSoap-request'.size() == 1
|
soapPostResponse.Body.'goodResponseSoap-request'.size() == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@Unroll
|
|
||||||
def "should dispatch rest mocks when second on #name"() {
|
def "should dispatch rest mocks when second on #name"() {
|
||||||
given:
|
given:
|
||||||
remoteMockServer.addMock(new AddMock(
|
remoteMockServer.addMock(new AddMock(
|
||||||
|
@ -283,7 +279,6 @@ class MockServerIntegrationTest extends Specification {
|
||||||
9998 | 'test2' | 'another port and path'
|
9998 | 'test2' | 'another port and path'
|
||||||
}
|
}
|
||||||
|
|
||||||
@Unroll
|
|
||||||
def "should dispatch rest mock with response code"() {
|
def "should dispatch rest mock with response code"() {
|
||||||
given:
|
given:
|
||||||
remoteMockServer.addMock(new AddMock(
|
remoteMockServer.addMock(new AddMock(
|
||||||
|
@ -857,7 +852,6 @@ class MockServerIntegrationTest extends Specification {
|
||||||
mockEvents2[0].response.statusCode == 202
|
mockEvents2[0].response.statusCode == 202
|
||||||
}
|
}
|
||||||
|
|
||||||
@Unroll
|
|
||||||
def "should return mock report with #mockEvents events when deleting mock with flag skip mock = #skipReport"() {
|
def "should return mock report with #mockEvents events when deleting mock with flag skip mock = #skipReport"() {
|
||||||
expect:
|
expect:
|
||||||
remoteMockServer.addMock(new AddMock(
|
remoteMockServer.addMock(new AddMock(
|
||||||
|
@ -885,7 +879,6 @@ class MockServerIntegrationTest extends Specification {
|
||||||
true | 0
|
true | 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Unroll
|
|
||||||
def "should reject mock when it has System.exit in closure"() {
|
def "should reject mock when it has System.exit in closure"() {
|
||||||
when:
|
when:
|
||||||
remoteMockServer.addMock(new AddMock(
|
remoteMockServer.addMock(new AddMock(
|
||||||
|
@ -1133,7 +1126,6 @@ class MockServerIntegrationTest extends Specification {
|
||||||
remoteMockServer.removeMock('testRest')?.size() == 1
|
remoteMockServer.removeMock('testRest')?.size() == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@Unroll
|
|
||||||
def 'should handle leading slash'() {
|
def 'should handle leading slash'() {
|
||||||
given:
|
given:
|
||||||
String name = "testRest-${UUID.randomUUID().toString()}"
|
String name = "testRest-${UUID.randomUUID().toString()}"
|
||||||
|
|
BIN
mockserver-tests/src/test/resources/keystore.jks
Normal file
BIN
mockserver-tests/src/test/resources/keystore.jks
Normal file
Binary file not shown.
BIN
mockserver-tests/src/test/resources/trusted.jks
Normal file
BIN
mockserver-tests/src/test/resources/trusted.jks
Normal file
Binary file not shown.
BIN
mockserver-tests/src/test/resources/truststore.jks
Normal file
BIN
mockserver-tests/src/test/resources/truststore.jks
Normal file
Binary file not shown.
BIN
mockserver-tests/src/test/resources/untrusted.jks
Normal file
BIN
mockserver-tests/src/test/resources/untrusted.jks
Normal file
Binary file not shown.
|
@ -10,15 +10,15 @@ import java.util.concurrent.CopyOnWriteArrayList
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@PackageScope
|
@PackageScope
|
||||||
class ContextExecutor {
|
class ContextExecutor {
|
||||||
private final HttpServerWraper httpServerWraper
|
private final HttpServerWrapper httpServerWrapper
|
||||||
final String path
|
final String path
|
||||||
private final List<Mock> mocks
|
private final List<Mock> mocks
|
||||||
|
|
||||||
ContextExecutor(HttpServerWraper httpServerWraper, Mock initialMock) {
|
ContextExecutor(HttpServerWrapper httpServerWrapper, Mock initialMock) {
|
||||||
this.httpServerWraper = httpServerWraper
|
this.httpServerWrapper = httpServerWrapper
|
||||||
this.path = "/${initialMock.path}"
|
this.path = "/${initialMock.path}"
|
||||||
this.mocks = new CopyOnWriteArrayList<>([initialMock])
|
this.mocks = new CopyOnWriteArrayList<>([initialMock])
|
||||||
httpServerWraper.createContext(path) {
|
httpServerWrapper.createContext(path) {
|
||||||
HttpExchange ex ->
|
HttpExchange ex ->
|
||||||
try {
|
try {
|
||||||
applyMocks(ex)
|
applyMocks(ex)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package pl.touk.mockserver.server
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpExchange
|
import com.sun.net.httpserver.HttpExchange
|
||||||
import groovy.util.logging.Slf4j
|
import groovy.util.logging.Slf4j
|
||||||
|
import pl.touk.mockserver.api.common.Https
|
||||||
import pl.touk.mockserver.api.common.ImportAlias
|
import pl.touk.mockserver.api.common.ImportAlias
|
||||||
import pl.touk.mockserver.api.common.Method
|
import pl.touk.mockserver.api.common.Method
|
||||||
import pl.touk.mockserver.api.request.AddMock
|
import pl.touk.mockserver.api.request.AddMock
|
||||||
|
@ -30,8 +31,8 @@ import static pl.touk.mockserver.server.Util.createResponse
|
||||||
@Slf4j
|
@Slf4j
|
||||||
class HttpMockServer {
|
class HttpMockServer {
|
||||||
|
|
||||||
private final HttpServerWraper httpServerWraper
|
private final HttpServerWrapper httpServerWrapper
|
||||||
private final Map<Integer, HttpServerWraper> childServers = new ConcurrentHashMap<>()
|
private final Map<Integer, HttpServerWrapper> childServers = new ConcurrentHashMap<>()
|
||||||
private final Set<String> mockNames = new CopyOnWriteArraySet<>()
|
private final Set<String> mockNames = new CopyOnWriteArraySet<>()
|
||||||
private final ConfigObject configuration = new ConfigObject()
|
private final ConfigObject configuration = new ConfigObject()
|
||||||
private final Executor executor
|
private final Executor executor
|
||||||
|
@ -41,13 +42,13 @@ class HttpMockServer {
|
||||||
|
|
||||||
HttpMockServer(int port = 9999, ConfigObject initialConfiguration = new ConfigObject(), int threads = 10) {
|
HttpMockServer(int port = 9999, ConfigObject initialConfiguration = new ConfigObject(), int threads = 10) {
|
||||||
executor = Executors.newFixedThreadPool(threads)
|
executor = Executors.newFixedThreadPool(threads)
|
||||||
httpServerWraper = new HttpServerWraper(port, executor)
|
httpServerWrapper = new HttpServerWrapper(port, executor)
|
||||||
|
|
||||||
initialConfiguration.values()?.each { ConfigObject co ->
|
initialConfiguration.values()?.each { ConfigObject co ->
|
||||||
addMock(co)
|
addMock(co)
|
||||||
}
|
}
|
||||||
|
|
||||||
httpServerWraper.createContext('/serverControl', {
|
httpServerWrapper.createContext('/serverControl', {
|
||||||
HttpExchange ex ->
|
HttpExchange ex ->
|
||||||
try {
|
try {
|
||||||
if (ex.requestMethod == 'GET') {
|
if (ex.requestMethod == 'GET') {
|
||||||
|
@ -108,7 +109,7 @@ class HttpMockServer {
|
||||||
throw new RuntimeException('mock already registered')
|
throw new RuntimeException('mock already registered')
|
||||||
}
|
}
|
||||||
Mock mock = mockFromRequest(request)
|
Mock mock = mockFromRequest(request)
|
||||||
HttpServerWraper child = getOrCreateChildServer(mock.port)
|
HttpServerWrapper child = getOrCreateChildServer(mock.port, mock.https)
|
||||||
child.addMock(mock)
|
child.addMock(mock)
|
||||||
saveConfiguration(request)
|
saveConfiguration(request)
|
||||||
mockNames << name
|
mockNames << name
|
||||||
|
@ -121,7 +122,7 @@ class HttpMockServer {
|
||||||
throw new RuntimeException('mock already registered')
|
throw new RuntimeException('mock already registered')
|
||||||
}
|
}
|
||||||
Mock mock = mockFromConfig(co)
|
Mock mock = mockFromConfig(co)
|
||||||
HttpServerWraper child = getOrCreateChildServer(mock.port)
|
HttpServerWrapper child = getOrCreateChildServer(mock.port, mock.https)
|
||||||
child.addMock(mock)
|
child.addMock(mock)
|
||||||
configuration.put(name, co)
|
configuration.put(name, co)
|
||||||
mockNames << name
|
mockNames << name
|
||||||
|
@ -156,6 +157,7 @@ class HttpMockServer {
|
||||||
mock.responseHeaders = request.responseHeaders
|
mock.responseHeaders = request.responseHeaders
|
||||||
mock.schema = request.schema
|
mock.schema = request.schema
|
||||||
mock.preserveHistory = request.preserveHistory != false
|
mock.preserveHistory = request.preserveHistory != false
|
||||||
|
mock.https = request.https
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,13 +172,23 @@ class HttpMockServer {
|
||||||
mock.responseHeaders = co.responseHeaders ?: null
|
mock.responseHeaders = co.responseHeaders ?: null
|
||||||
mock.schema = co.schema ?: null
|
mock.schema = co.schema ?: null
|
||||||
mock.preserveHistory = co.preserveHistory != false
|
mock.preserveHistory = co.preserveHistory != false
|
||||||
|
if (co.https) {
|
||||||
|
mock.https = new Https(
|
||||||
|
keystorePath: co.https.keystorePath ?: null,
|
||||||
|
keystorePassword: co.https.keystorePassword,
|
||||||
|
keyPassword: co.https.keyPassword,
|
||||||
|
truststorePath: co.https.truststorePath,
|
||||||
|
truststorePassword: co.https.truststorePassword,
|
||||||
|
requireClientAuth: co.https?.requireClientAuth?.asBoolean() ?: false
|
||||||
|
)
|
||||||
|
}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpServerWraper getOrCreateChildServer(int mockPort) {
|
private HttpServerWrapper getOrCreateChildServer(int mockPort, Https https) {
|
||||||
HttpServerWraper child = childServers[mockPort]
|
HttpServerWrapper child = childServers[mockPort]
|
||||||
if (!child) {
|
if (!child) {
|
||||||
child = new HttpServerWraper(mockPort, executor)
|
child = new HttpServerWrapper(mockPort, executor, https)
|
||||||
childServers.put(mockPort, child)
|
childServers.put(mockPort, child)
|
||||||
}
|
}
|
||||||
return child
|
return child
|
||||||
|
@ -244,6 +256,6 @@ class HttpMockServer {
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
childServers.values().each { it.stop() }
|
childServers.values().each { it.stop() }
|
||||||
httpServerWraper.stop()
|
httpServerWrapper.stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
package pl.touk.mockserver.server
|
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpHandler
|
|
||||||
import com.sun.net.httpserver.HttpServer
|
|
||||||
import groovy.transform.PackageScope
|
|
||||||
import groovy.util.logging.Slf4j
|
|
||||||
|
|
||||||
import java.util.concurrent.Executor
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@PackageScope
|
|
||||||
class HttpServerWraper {
|
|
||||||
private final HttpServer httpServer
|
|
||||||
final int port
|
|
||||||
|
|
||||||
private List<ContextExecutor> executors = []
|
|
||||||
|
|
||||||
HttpServerWraper(int port, Executor executor) {
|
|
||||||
this.port = port
|
|
||||||
InetSocketAddress addr = new InetSocketAddress(Inet4Address.getByName("0.0.0.0"), port)
|
|
||||||
httpServer = HttpServer.create(addr, 0)
|
|
||||||
httpServer.executor = executor
|
|
||||||
log.info("Http server starting on port $port...")
|
|
||||||
httpServer.start()
|
|
||||||
log.info('Http server is started')
|
|
||||||
}
|
|
||||||
|
|
||||||
void createContext(String context, HttpHandler handler) {
|
|
||||||
httpServer.createContext(context, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
void addMock(Mock mock) {
|
|
||||||
ContextExecutor executor = executors.find { it.path == mock.path }
|
|
||||||
if (executor) {
|
|
||||||
executor.addMock(mock)
|
|
||||||
} else {
|
|
||||||
executors << new ContextExecutor(this, mock)
|
|
||||||
}
|
|
||||||
log.info("Added mock ${mock.name}")
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop() {
|
|
||||||
executors.each { httpServer.removeContext(it.contextPath) }
|
|
||||||
httpServer.stop(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MockEvent> removeMock(String name) {
|
|
||||||
return executors.collect { it.removeMock(name) }.flatten() as List<MockEvent>
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MockEvent> peekMock(String name) {
|
|
||||||
return executors.collect { it.peekMock(name) }.flatten() as List<MockEvent>
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Mock> getMocks() {
|
|
||||||
return executors.collect { it.mocks }.flatten() as List<Mock>
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
package pl.touk.mockserver.server
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpHandler
|
||||||
|
import com.sun.net.httpserver.HttpServer
|
||||||
|
import com.sun.net.httpserver.HttpsServer
|
||||||
|
import groovy.transform.PackageScope
|
||||||
|
import groovy.util.logging.Slf4j
|
||||||
|
import pl.touk.mockserver.api.common.Https
|
||||||
|
|
||||||
|
import javax.net.ssl.KeyManager
|
||||||
|
import javax.net.ssl.KeyManagerFactory
|
||||||
|
import javax.net.ssl.SSLContext
|
||||||
|
import javax.net.ssl.TrustManager
|
||||||
|
import javax.net.ssl.TrustManagerFactory
|
||||||
|
import java.security.KeyStore
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import java.util.concurrent.Executor
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@PackageScope
|
||||||
|
class HttpServerWrapper {
|
||||||
|
private final HttpServer httpServer
|
||||||
|
final int port
|
||||||
|
|
||||||
|
private List<ContextExecutor> executors = []
|
||||||
|
|
||||||
|
HttpServerWrapper(int port, Executor executor, Https https = null) {
|
||||||
|
this.port = port
|
||||||
|
InetSocketAddress addr = new InetSocketAddress(Inet4Address.getByName("0.0.0.0"), port)
|
||||||
|
httpServer = buildServer(addr, https)
|
||||||
|
httpServer.executor = executor
|
||||||
|
log.info("Http server starting on port $port...")
|
||||||
|
httpServer.start()
|
||||||
|
log.info('Http server is started')
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpServer buildServer(InetSocketAddress addr, Https https) {
|
||||||
|
if (https) {
|
||||||
|
HttpsServer httpsServer = HttpsServer.create(addr, 0)
|
||||||
|
httpsServer.httpsConfigurator = new HttpsConfig(buildSslContext(https), https)
|
||||||
|
return httpsServer
|
||||||
|
} else {
|
||||||
|
return HttpServer.create(addr, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SSLContext buildSslContext(Https https) {
|
||||||
|
KeyManager[] keyManagers = buildKeyManager(https)
|
||||||
|
TrustManager[] trustManagers = buildTrustManager(https)
|
||||||
|
|
||||||
|
SSLContext ssl = SSLContext.getInstance('TLSv1')
|
||||||
|
ssl.init(keyManagers, trustManagers, new SecureRandom())
|
||||||
|
return ssl
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyManager[] buildKeyManager(Https https) {
|
||||||
|
KeyStore keyStore = KeyStore.getInstance('jks')
|
||||||
|
keyStore.load(new FileInputStream(https.keystorePath), https.keystorePassword.toCharArray())
|
||||||
|
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.defaultAlgorithm)
|
||||||
|
kmf.init(keyStore, https.keyPassword.toCharArray())
|
||||||
|
return kmf.keyManagers
|
||||||
|
}
|
||||||
|
|
||||||
|
private TrustManager[] buildTrustManager(Https https) {
|
||||||
|
if (https.requireClientAuth) {
|
||||||
|
KeyStore trustStore = KeyStore.getInstance('jks')
|
||||||
|
trustStore.load(new FileInputStream(https.truststorePath), https.truststorePassword.toCharArray())
|
||||||
|
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.defaultAlgorithm)
|
||||||
|
tmf.init(trustStore)
|
||||||
|
return tmf.trustManagers
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void createContext(String context, HttpHandler handler) {
|
||||||
|
httpServer.createContext(context, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
void addMock(Mock mock) {
|
||||||
|
ContextExecutor executor = executors.find { it.path == mock.path }
|
||||||
|
if (executor) {
|
||||||
|
executor.addMock(mock)
|
||||||
|
} else {
|
||||||
|
executors << new ContextExecutor(this, mock)
|
||||||
|
}
|
||||||
|
log.info("Added mock ${mock.name}")
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
executors.each { httpServer.removeContext(it.contextPath) }
|
||||||
|
httpServer.stop(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MockEvent> removeMock(String name) {
|
||||||
|
return executors.collect { it.removeMock(name) }.flatten() as List<MockEvent>
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MockEvent> peekMock(String name) {
|
||||||
|
return executors.collect { it.peekMock(name) }.flatten() as List<MockEvent>
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Mock> getMocks() {
|
||||||
|
return executors.collect { it.mocks }.flatten() as List<Mock>
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package pl.touk.mockserver.server
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpsConfigurator
|
||||||
|
import com.sun.net.httpserver.HttpsParameters
|
||||||
|
import groovy.transform.CompileStatic
|
||||||
|
import pl.touk.mockserver.api.common.Https
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext
|
||||||
|
import javax.net.ssl.SSLParameters
|
||||||
|
|
||||||
|
@CompileStatic
|
||||||
|
class HttpsConfig extends HttpsConfigurator {
|
||||||
|
private final Https https
|
||||||
|
|
||||||
|
HttpsConfig(SSLContext sslContext, Https https) {
|
||||||
|
super(sslContext)
|
||||||
|
this.https = https
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void configure(HttpsParameters httpsParameters) {
|
||||||
|
SSLContext sslContext = getSSLContext()
|
||||||
|
SSLParameters sslParameters = sslContext.defaultSSLParameters
|
||||||
|
sslParameters.needClientAuth = https.requireClientAuth
|
||||||
|
httpsParameters.needClientAuth = https.requireClientAuth
|
||||||
|
httpsParameters.SSLParameters = sslParameters
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import groovy.transform.PackageScope
|
||||||
import groovy.util.logging.Slf4j
|
import groovy.util.logging.Slf4j
|
||||||
import org.codehaus.groovy.control.CompilerConfiguration
|
import org.codehaus.groovy.control.CompilerConfiguration
|
||||||
import org.codehaus.groovy.control.customizers.ImportCustomizer
|
import org.codehaus.groovy.control.customizers.ImportCustomizer
|
||||||
|
import pl.touk.mockserver.api.common.Https
|
||||||
import pl.touk.mockserver.api.common.Method
|
import pl.touk.mockserver.api.common.Method
|
||||||
|
|
||||||
import javax.xml.XMLConstants
|
import javax.xml.XMLConstants
|
||||||
|
@ -35,6 +36,7 @@ class Mock implements Comparable<Mock> {
|
||||||
private Validator validator
|
private Validator validator
|
||||||
Map<String, String> imports = [:]
|
Map<String, String> imports = [:]
|
||||||
boolean preserveHistory = true
|
boolean preserveHistory = true
|
||||||
|
Https https
|
||||||
|
|
||||||
Mock(String name, String path, int port) {
|
Mock(String name, String path, int port) {
|
||||||
if (!(name)) {
|
if (!(name)) {
|
||||||
|
|
7
pom.xml
7
pom.xml
|
@ -36,6 +36,7 @@
|
||||||
<jmh.version>1.11.2</jmh.version>
|
<jmh.version>1.11.2</jmh.version>
|
||||||
<maven-release-plugin.version>2.5.2</maven-release-plugin.version>
|
<maven-release-plugin.version>2.5.2</maven-release-plugin.version>
|
||||||
<gmavenplus-plugin.version>1.4</gmavenplus-plugin.version>
|
<gmavenplus-plugin.version>1.4</gmavenplus-plugin.version>
|
||||||
|
<spock-global-unroll.version>0.5.1</spock-global-unroll.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<scm>
|
<scm>
|
||||||
|
@ -99,6 +100,12 @@
|
||||||
<version>${jmh.version}</version>
|
<version>${jmh.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>info.solidsoft.spock</groupId>
|
||||||
|
<artifactId>spock-global-unroll</artifactId>
|
||||||
|
<version>${spock-global-unroll.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue