Split project to modules
This commit is contained in:
parent
1bbaf72084
commit
73b3630e2f
26 changed files with 145 additions and 34 deletions
29
mockserver/pom.xml
Normal file
29
mockserver/pom.xml
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>http-mock-server</artifactId>
|
||||
<groupId>pl.touk.mockserver</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>mockserver</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.groovy</groupId>
|
||||
<artifactId>groovy-all</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,70 @@
|
|||
package pl.touk.mockserver.server
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange
|
||||
import groovy.transform.PackageScope
|
||||
import groovy.util.logging.Slf4j
|
||||
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
@Slf4j
|
||||
@PackageScope
|
||||
class ContextExecutor {
|
||||
private final HttpServerWraper httpServerWraper
|
||||
final String path
|
||||
private final List<Mock> mocks
|
||||
|
||||
ContextExecutor(HttpServerWraper httpServerWraper, Mock initialMock) {
|
||||
this.httpServerWraper = httpServerWraper
|
||||
this.path = '/' + initialMock.path
|
||||
this.mocks = new CopyOnWriteArrayList<>([initialMock])
|
||||
httpServerWraper.createContext(path, {
|
||||
HttpExchange ex ->
|
||||
MockRequest request = new MockRequest(ex.requestBody.text, ex.requestHeaders, ex.requestURI)
|
||||
log.info("Mock received input")
|
||||
for (Mock mock : mocks) {
|
||||
try {
|
||||
if (mock.match(ex.requestMethod, request)) {
|
||||
MockResponse httpResponse = mock.apply(request)
|
||||
fillExchange(ex, httpResponse)
|
||||
return
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
Util.createResponse(ex, request.text, 404)
|
||||
})
|
||||
}
|
||||
|
||||
String getPath() {
|
||||
return path.substring(1)
|
||||
}
|
||||
|
||||
String getContextPath() {
|
||||
return path
|
||||
}
|
||||
|
||||
private static void fillExchange(HttpExchange httpExchange, MockResponse response) {
|
||||
response.headers.each {
|
||||
httpExchange.responseHeaders.add(it.key, it.value)
|
||||
}
|
||||
Util.createResponse(httpExchange, response.text, response.statusCode)
|
||||
}
|
||||
|
||||
List<MockEvent> removeMock(String name) {
|
||||
Mock mock = mocks.find { it.name == name }
|
||||
if (mock) {
|
||||
mocks.remove(mock)
|
||||
return mock.history
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
void addMock(Mock mock) {
|
||||
mocks << mock
|
||||
}
|
||||
|
||||
List<Mock> getMocks() {
|
||||
return mocks
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
package pl.touk.mockserver.server
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange
|
||||
import groovy.util.logging.Slf4j
|
||||
import groovy.util.slurpersupport.GPathResult
|
||||
import groovy.xml.MarkupBuilder
|
||||
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.CopyOnWriteArraySet
|
||||
|
||||
import static pl.touk.mockserver.server.Util.createResponse
|
||||
|
||||
@Slf4j
|
||||
class HttpMockServer {
|
||||
|
||||
private final HttpServerWraper httpServerWraper
|
||||
private final List<HttpServerWraper> childServers = new CopyOnWriteArrayList<>()
|
||||
private final Set<String> mockNames = new CopyOnWriteArraySet<>()
|
||||
|
||||
HttpMockServer(int port = 9999) {
|
||||
httpServerWraper = new HttpServerWraper(port)
|
||||
|
||||
httpServerWraper.createContext('/serverControl', {
|
||||
HttpExchange ex ->
|
||||
try {
|
||||
if (ex.requestMethod == 'GET') {
|
||||
listMocks(ex)
|
||||
} else if (ex.requestMethod == 'POST') {
|
||||
GPathResult request = new XmlSlurper().parse(ex.requestBody)
|
||||
if (request.name() == 'addMock') {
|
||||
addMock(request, ex)
|
||||
} else if (request.name() == 'removeMock') {
|
||||
removeMock(request, ex)
|
||||
} else {
|
||||
throw new RuntimeException('Unknown request')
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException('Unknown request')
|
||||
}
|
||||
} catch (Exception e) {
|
||||
createErrorResponse(ex, e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
void listMocks(HttpExchange ex) {
|
||||
StringWriter sw = new StringWriter()
|
||||
MarkupBuilder builder = new MarkupBuilder(sw)
|
||||
builder.mocks {
|
||||
listMocks().each {
|
||||
Mock mock ->
|
||||
builder.mock {
|
||||
name mock.name
|
||||
path mock.path
|
||||
port mock.port
|
||||
}
|
||||
}
|
||||
}
|
||||
createResponse(ex, sw.toString(), 200)
|
||||
}
|
||||
|
||||
Set<Mock> listMocks() {
|
||||
return childServers.collect { it.mocks }.flatten() as TreeSet
|
||||
}
|
||||
|
||||
private void addMock(GPathResult request, HttpExchange ex) {
|
||||
String name = request.name
|
||||
if (name in mockNames) {
|
||||
throw new RuntimeException('mock already registered')
|
||||
}
|
||||
Mock mock = mockFromRequest(request)
|
||||
HttpServerWraper child = getOrCreateChildServer(mock.port)
|
||||
child.addMock(mock)
|
||||
mockNames << name
|
||||
createResponse(ex, '<mockAdded/>', 200)
|
||||
}
|
||||
|
||||
private static Mock mockFromRequest(GPathResult request) {
|
||||
String name = request.name
|
||||
String mockPath = request.path
|
||||
int mockPort = Integer.valueOf(request.port as String)
|
||||
Mock mock = new Mock(name, mockPath, mockPort)
|
||||
mock.predicate = request.predicate
|
||||
mock.response = request.response
|
||||
mock.soap = request.soap
|
||||
mock.statusCode = request.statusCode
|
||||
mock.method = request.method
|
||||
mock.responseHeaders = request.responseHeaders
|
||||
return mock
|
||||
}
|
||||
|
||||
private HttpServerWraper getOrCreateChildServer(int mockPort) {
|
||||
HttpServerWraper child = childServers.find { it.port == mockPort }
|
||||
if (!child) {
|
||||
child = new HttpServerWraper(mockPort)
|
||||
childServers << child
|
||||
}
|
||||
return child
|
||||
}
|
||||
|
||||
private void removeMock(GPathResult request, HttpExchange ex) {
|
||||
String name = request.name
|
||||
if (!(name in mockNames)) {
|
||||
throw new RuntimeException('mock not registered')
|
||||
}
|
||||
log.info("Removing mock $name")
|
||||
List<MockEvent> mockEvents = childServers.collect { it.removeMock(name) }.flatten()
|
||||
mockNames.remove(name)
|
||||
StringWriter sw = new StringWriter()
|
||||
MarkupBuilder builder = new MarkupBuilder(sw)
|
||||
builder.mockRemoved {
|
||||
mockEvents.each { MockEvent m ->
|
||||
builder.mockEvent {
|
||||
builder.request {
|
||||
text m.request.text
|
||||
headers {
|
||||
m.request.headers.each {
|
||||
builder.param(name: it.key, it.value)
|
||||
}
|
||||
}
|
||||
query {
|
||||
m.request.query.each {
|
||||
builder.param(name: it.key, it.value)
|
||||
}
|
||||
}
|
||||
path {
|
||||
m.request.path.each {
|
||||
builder.elem it
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.response {
|
||||
text m.response.text
|
||||
headers {
|
||||
m.response.headers.each {
|
||||
builder.param(name: it.key, it.value)
|
||||
}
|
||||
}
|
||||
statusCode m.response.statusCode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
createResponse(ex, sw.toString(), 200)
|
||||
}
|
||||
|
||||
private static void createErrorResponse(HttpExchange ex, Exception e) {
|
||||
StringWriter sw = new StringWriter()
|
||||
MarkupBuilder builder = new MarkupBuilder(sw)
|
||||
builder.exceptionOccured e.message
|
||||
createResponse(ex, sw.toString(), 400)
|
||||
}
|
||||
|
||||
void stop() {
|
||||
childServers.each { it.stop() }
|
||||
httpServerWraper.stop()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
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.Executors
|
||||
|
||||
@Slf4j
|
||||
@PackageScope
|
||||
class HttpServerWraper {
|
||||
private final HttpServer httpServer
|
||||
final int port
|
||||
|
||||
private List<ContextExecutor> executors = []
|
||||
|
||||
HttpServerWraper(int port) {
|
||||
this.port = port
|
||||
InetSocketAddress addr = new InetSocketAddress(Inet4Address.getByName("0.0.0.0"), port)
|
||||
httpServer = HttpServer.create(addr, 0)
|
||||
httpServer.executor = Executors.newCachedThreadPool()
|
||||
log.info("Http server statrting 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()
|
||||
}
|
||||
|
||||
List<Mock> getMocks() {
|
||||
return executors.collect { it.mocks }.flatten()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package pl.touk.mockserver.server
|
||||
|
||||
import groovy.util.logging.Slf4j
|
||||
|
||||
@Slf4j
|
||||
class Main {
|
||||
static void main(String[] args) {
|
||||
HttpMockServer httpMockServer = new HttpMockServer()
|
||||
|
||||
Runtime.runtime.addShutdownHook(new Thread({
|
||||
log.info('Http server is stopping...')
|
||||
httpMockServer.stop()
|
||||
log.info('Http server is stopped')
|
||||
} as Runnable))
|
||||
|
||||
while (true) {
|
||||
Thread.sleep(10000)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package pl.touk.mockserver.server
|
||||
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import groovy.transform.PackageScope
|
||||
import groovy.util.logging.Slf4j
|
||||
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
@PackageScope
|
||||
@EqualsAndHashCode(excludes = ["counter"])
|
||||
@Slf4j
|
||||
class Mock implements Comparable<Mock> {
|
||||
final String name
|
||||
final String path
|
||||
final int port
|
||||
Closure predicate = { _ -> true }
|
||||
Closure response = { _ -> '' }
|
||||
Closure responseHeaders = { _ -> [:] }
|
||||
boolean soap = false
|
||||
int statusCode = 200
|
||||
String method = 'POST'
|
||||
int counter = 0
|
||||
final List<MockEvent> history = new CopyOnWriteArrayList<>()
|
||||
|
||||
Mock(String name, String path, int port) {
|
||||
if (!(name)) {
|
||||
throw new RuntimeException("Mock name must be given")
|
||||
}
|
||||
this.name = name
|
||||
this.path = path
|
||||
this.port = port
|
||||
}
|
||||
|
||||
boolean match(String method, MockRequest request) {
|
||||
return this.method == method && predicate(request)
|
||||
}
|
||||
|
||||
MockResponse apply(MockRequest request) {
|
||||
log.debug("Mock $name invoked")
|
||||
++counter
|
||||
String responseText = response(request)
|
||||
String response = soap ? wrapSoap(responseText) : responseText
|
||||
Map<String, String> headers = responseHeaders(request)
|
||||
MockResponse mockResponse = new MockResponse(statusCode, response, headers)
|
||||
history << new MockEvent(request, mockResponse)
|
||||
return mockResponse
|
||||
}
|
||||
|
||||
private static String wrapSoap(String request) {
|
||||
"""<?xml version='1.0' encoding='UTF-8'?>
|
||||
<soap-env:Envelope xmlns:soap-env='http://schemas.xmlsoap.org/soap/envelope/' xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">
|
||||
<soap-env:Body>${request}</soap-env:Body>
|
||||
</soap-env:Envelope>"""
|
||||
}
|
||||
|
||||
void setPredicate(String predicate) {
|
||||
if (predicate) {
|
||||
this.predicate = Eval.me(predicate) as Closure
|
||||
}
|
||||
}
|
||||
|
||||
void setResponse(String response) {
|
||||
if (response) {
|
||||
this.response = Eval.me(response) as Closure
|
||||
}
|
||||
}
|
||||
|
||||
void setSoap(String soap) {
|
||||
if (soap) {
|
||||
this.soap = Boolean.valueOf(soap)
|
||||
}
|
||||
}
|
||||
|
||||
void setStatusCode(String statusCode) {
|
||||
if (statusCode) {
|
||||
this.statusCode = Integer.valueOf(statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
void setMethod(String method) {
|
||||
if (method) {
|
||||
this.method = method
|
||||
}
|
||||
}
|
||||
|
||||
void setResponseHeaders(String responseHeaders) {
|
||||
if (responseHeaders) {
|
||||
this.responseHeaders = Eval.me(responseHeaders) as Closure
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
int compareTo(Mock o) {
|
||||
return name.compareTo(o.name)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package pl.touk.mockserver.server
|
||||
|
||||
import groovy.transform.PackageScope
|
||||
|
||||
@PackageScope
|
||||
class MockEvent {
|
||||
final MockRequest request
|
||||
final MockResponse response
|
||||
|
||||
MockEvent(MockRequest request, MockResponse response) {
|
||||
this.request = request
|
||||
this.response = response
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package pl.touk.mockserver.server
|
||||
|
||||
import com.sun.net.httpserver.Headers
|
||||
import groovy.json.JsonSlurper
|
||||
import groovy.transform.PackageScope
|
||||
import groovy.util.slurpersupport.GPathResult
|
||||
|
||||
@PackageScope
|
||||
class MockRequest {
|
||||
final String text
|
||||
final Map<String, String> headers
|
||||
final Map<String, String> query
|
||||
final GPathResult xml
|
||||
final GPathResult soap
|
||||
final Object json
|
||||
final List<String> path
|
||||
|
||||
MockRequest(String text, Headers headers, URI uri) {
|
||||
this.text = text
|
||||
this.headers = headersToMap(headers)
|
||||
this.query = queryParamsToMap(uri.query)
|
||||
this.xml = inputToXml(text)
|
||||
this.soap = inputToSoap(xml)
|
||||
this.json = inputToJson(text)
|
||||
this.path = uri.path.split('/').findAll()
|
||||
}
|
||||
|
||||
private static GPathResult inputToXml(String text) {
|
||||
try {
|
||||
return new XmlSlurper().parseText(text)
|
||||
} catch (Exception _) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private static GPathResult inputToSoap(GPathResult xml) {
|
||||
try {
|
||||
if (xml.name() == 'Envelope' && xml.Body.size() > 0) {
|
||||
return getSoapBodyContent(xml)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
} catch (Exception _) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private static GPathResult getSoapBodyContent(GPathResult xml) {
|
||||
return xml.Body.'**'[1]
|
||||
}
|
||||
|
||||
private static Object inputToJson(String text) {
|
||||
try {
|
||||
return new JsonSlurper().parseText(text)
|
||||
} catch (Exception _) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, String> queryParamsToMap(String query) {
|
||||
return query?.split('&')?.collectEntries {
|
||||
String[] keyValue = it.split('='); [(keyValue[0]): keyValue[1]]
|
||||
} ?: [:]
|
||||
}
|
||||
|
||||
private static Map<String, String> headersToMap(Headers headers) {
|
||||
return headers.collectEntries {
|
||||
[it.key.toLowerCase(), it.value.join(',')]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package pl.touk.mockserver.server
|
||||
|
||||
import groovy.transform.PackageScope
|
||||
|
||||
@PackageScope
|
||||
class MockResponse {
|
||||
final int statusCode
|
||||
final String text
|
||||
final Map<String, String> headers
|
||||
|
||||
MockResponse(int statusCode, String text, Map<String, String> headers) {
|
||||
this.statusCode = statusCode
|
||||
this.text = text
|
||||
this.headers = headers
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package pl.touk.mockserver.server
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange
|
||||
|
||||
class Util {
|
||||
static void createResponse(HttpExchange ex, String response, int statusCode) {
|
||||
ex.sendResponseHeaders(statusCode, response ? response.length() : -1)
|
||||
if (response) {
|
||||
ex.responseBody << response
|
||||
ex.responseBody.close()
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue