├── .gitignore ├── README.md ├── pom.xml ├── src └── main │ ├── java │ └── com │ │ └── demo │ │ └── mockservice │ │ ├── App.java │ │ ├── config │ │ ├── FilterConfig.java │ │ ├── HttpTraceActuatorConfiguration.java │ │ └── TraceRequestFilter.java │ │ ├── controller │ │ └── MockServerDemoController.java │ │ ├── dto │ │ ├── Book.java │ │ ├── BookSearchRequest.java │ │ ├── BookSearchResponse.java │ │ └── DynamicResponseBookSearchRequest.java │ │ ├── filter │ │ ├── DynamicResponseFilter.java │ │ └── RequestWrapper.java │ │ └── util │ │ ├── FileReader.java │ │ └── RandomDataGenerator.java │ └── resources │ ├── application.yml │ └── requests │ └── response1.xml └── start_jar.sh /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .project 3 | .settings/ 4 | .classpath 5 | .idea 6 | **/*.iml 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mock Server 2 | 3 | Ready-to-use mock data server. You can quickly create HTTP API's which returns mock data. 4 | 5 |


6 | 7 | ## Usage (How to create HTTP API with mock data) 8 | 9 | There are 3 way to generate mock data: 10 | 11 | - ### 1 - RandomDataGenerator 12 | 13 | You can generate fake response with random data. 14 | 15 | See this DEMO method: ["MockServerDemoController class --> simpleFakeData() method"](./src/main/java/com/demo/mockservice/controller/MockServerDemoController.java#L29) 16 | 17 |
18 | 19 | - ### 2 - Dynamic Response 20 | 21 | The server will response you with the data which you send it in a special field. 22 | 23 | You don't even need to write a code for that. 24 | 25 | See this DEMO method: ["MockServerDemoController class --> dynamicResponseXML() method"](./src/main/java/com/demo/mockservice/controller/MockServerDemoController.java#L53) 26 | 27 |
28 | 29 | - ### 3 - Response from file 30 | 31 | You can return any file as a response. 32 | 33 | See this DEMO method: ["MockServerDemoController class --> resultFromFile() method"](./src/main/java/com/demo/mockservice/controller/MockServerDemoController.java#L41) 34 | 35 |


36 | 37 | ## HTTP Request and Response Logs 38 | 39 | You can check the request and response from: 40 | 41 | - Only latest 100 request and response: 42 | 43 | http://localhost:8088/actuator/httpexchanges 44 | 45 | - Server console logs 46 | 47 |


48 | 49 | ## Swagger Docs 50 | 51 | You may need to test mock services directly from HTML (Web) UI of Swagger. 52 | 53 | - Swagger HTML (Web) UI 54 | 55 | http://localhost:8088/swagger-ui.html 56 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.3.1 9 | 10 | 11 | jar 12 | com.demo 13 | mock_data_server 14 | 0.0.1-SNAPSHOT 15 | 16 | 21 17 | 21 18 | 21 19 | 20 | 21 | 22 | org.zalando 23 | logbook-spring-boot-starter 24 | 3.9.0 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-actuator 29 | 30 | 31 | com.fasterxml.jackson.dataformat 32 | jackson-dataformat-xml 33 | 34 | 35 | org.projectlombok 36 | lombok 37 | 1.18.34 38 | provided 39 | 40 | 41 | org.jeasy 42 | easy-random-core 43 | 5.0.0 44 | 45 | 46 | org.springdoc 47 | springdoc-openapi-starter-webmvc-ui 48 | 2.6.0 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-starter-web 53 | 54 | 55 | 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-maven-plugin 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/main/java/com/demo/mockservice/App.java: -------------------------------------------------------------------------------- 1 | package com.demo.mockservice; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class App { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(App.class, args); 11 | } 12 | } -------------------------------------------------------------------------------- /src/main/java/com/demo/mockservice/config/FilterConfig.java: -------------------------------------------------------------------------------- 1 | package com.demo.mockservice.config; 2 | 3 | import com.demo.mockservice.filter.DynamicResponseFilter; 4 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 5 | import org.springframework.context.annotation.Bean; 6 | 7 | public class FilterConfig { 8 | 9 | @Bean 10 | public FilterRegistrationBean automaticResponseGeneratorFilter() { 11 | FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); 12 | registrationBean.setFilter(new DynamicResponseFilter()); 13 | registrationBean.addUrlPatterns("/*"); 14 | return registrationBean; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/demo/mockservice/config/HttpTraceActuatorConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.demo.mockservice.config; 2 | 3 | 4 | import org.springframework.boot.actuate.web.exchanges.HttpExchangeRepository; 5 | import org.springframework.boot.actuate.web.exchanges.InMemoryHttpExchangeRepository; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | @Configuration 10 | public class HttpTraceActuatorConfiguration { 11 | 12 | @Bean 13 | // HTTP Trace actuator 14 | public HttpExchangeRepository httpTraceRepository() { 15 | return new InMemoryHttpExchangeRepository(); 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/java/com/demo/mockservice/config/TraceRequestFilter.java: -------------------------------------------------------------------------------- 1 | package com.demo.mockservice.config; 2 | 3 | import com.demo.mockservice.filter.DynamicResponseFilter; 4 | import org.springframework.boot.actuate.web.exchanges.HttpExchangeRepository; 5 | import org.springframework.boot.actuate.web.exchanges.Include; 6 | import org.springframework.boot.actuate.web.exchanges.servlet.HttpExchangesFilter; 7 | import org.springframework.stereotype.Component; 8 | 9 | import jakarta.servlet.http.HttpServletRequest; 10 | import java.util.Arrays; 11 | 12 | @Component 13 | // HTTP Trace actuator filter (excludes the "swagger", "actuator" requests) 14 | public class TraceRequestFilter extends HttpExchangesFilter { 15 | 16 | public TraceRequestFilter(HttpExchangeRepository repository) { 17 | super(repository, Include.defaultIncludes()); 18 | } 19 | 20 | @Override 21 | protected boolean shouldNotFilter(HttpServletRequest request) { 22 | if (request.getServletPath().equals("/")) { 23 | return true; 24 | } 25 | return Arrays.stream(DynamicResponseFilter.EXCLUDE_URL_LIST).anyMatch(request.getServletPath()::contains); 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/java/com/demo/mockservice/controller/MockServerDemoController.java: -------------------------------------------------------------------------------- 1 | package com.demo.mockservice.controller; 2 | 3 | import com.demo.mockservice.dto.BookSearchRequest; 4 | import com.demo.mockservice.dto.BookSearchResponse; 5 | import com.demo.mockservice.dto.DynamicResponseBookSearchRequest; 6 | import com.demo.mockservice.util.FileReader; 7 | import com.demo.mockservice.util.RandomDataGenerator; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.http.MediaType; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RequestMethod; 14 | import org.springframework.web.bind.annotation.RestController; 15 | 16 | /* 17 | This Controller is using to test the mock server itself. 18 | */ 19 | @RequestMapping("/mockServerDemoController") 20 | @RestController 21 | public class MockServerDemoController { 22 | 23 | private final Logger logger = LoggerFactory.getLogger(MockServerDemoController.class); 24 | 25 | @RequestMapping(value = "/simpleFakeData", 26 | method = RequestMethod.POST, 27 | consumes = MediaType.APPLICATION_XML_VALUE, 28 | produces = MediaType.APPLICATION_XML_VALUE) 29 | public BookSearchResponse simpleFakeData(@RequestBody BookSearchRequest bookSearchRequest) { 30 | 31 | BookSearchResponse result = RandomDataGenerator.generateRandomData(BookSearchResponse.class); 32 | // Optional custom hard manipulations for "result" instance. 33 | result.setResponseId("This is hard manipulation example - from Java controller"); 34 | return result; 35 | } 36 | 37 | @RequestMapping(value = "/resultFromFile", 38 | method = RequestMethod.POST, 39 | consumes = MediaType.APPLICATION_XML_VALUE, 40 | produces = MediaType.APPLICATION_XML_VALUE) 41 | public String resultFromFile(@RequestBody BookSearchRequest bookSearchRequest) { 42 | 43 | return FileReader.readFile("response1.xml"); 44 | } 45 | 46 | /* 47 | You don't need below 2 methods. Below methods added here, so we can see them from Swagger-web-UI. 48 | */ 49 | @RequestMapping(value = "/dynamicResponseXML", 50 | method = RequestMethod.POST, 51 | consumes = MediaType.APPLICATION_XML_VALUE, 52 | produces = MediaType.APPLICATION_XML_VALUE) 53 | public BookSearchResponse dynamicResponseXML(@RequestBody DynamicResponseBookSearchRequest bookSearchRequest) { 54 | 55 | return null; 56 | } 57 | 58 | @RequestMapping(value = "/dynamicResponseJSON", 59 | method = RequestMethod.POST, 60 | consumes = MediaType.APPLICATION_JSON_VALUE, 61 | produces = MediaType.APPLICATION_JSON_VALUE) 62 | public BookSearchResponse dynamicResponseJSON(@RequestBody DynamicResponseBookSearchRequest bookSearchRequest) { 63 | 64 | return null; 65 | } 66 | } -------------------------------------------------------------------------------- /src/main/java/com/demo/mockservice/dto/Book.java: -------------------------------------------------------------------------------- 1 | package com.demo.mockservice.dto; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class Book { 7 | private String bookName; 8 | private String bookId; 9 | } -------------------------------------------------------------------------------- /src/main/java/com/demo/mockservice/dto/BookSearchRequest.java: -------------------------------------------------------------------------------- 1 | package com.demo.mockservice.dto; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class BookSearchRequest { 7 | private int maxPage; 8 | private String bookName; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/demo/mockservice/dto/BookSearchResponse.java: -------------------------------------------------------------------------------- 1 | package com.demo.mockservice.dto; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | @Data 8 | public class BookSearchResponse { 9 | private int maxPage; 10 | private List results; 11 | private String responseId; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/demo/mockservice/dto/DynamicResponseBookSearchRequest.java: -------------------------------------------------------------------------------- 1 | package com.demo.mockservice.dto; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class DynamicResponseBookSearchRequest { 8 | 9 | private int maxPage; 10 | 11 | private String bookName; 12 | 13 | @Schema( 14 | title = "This value will be return as response automatically from mock-server.", 15 | example = "{ 'customer': 'mehmet', 'book': { 'name': 'davinci code' } }") 16 | private String mockServerResponseData; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/demo/mockservice/filter/DynamicResponseFilter.java: -------------------------------------------------------------------------------- 1 | package com.demo.mockservice.filter; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.core.TreeNode; 5 | import com.fasterxml.jackson.databind.JsonNode; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import com.fasterxml.jackson.dataformat.xml.XmlMapper; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.http.MediaType; 11 | import org.springframework.stereotype.Component; 12 | 13 | import jakarta.servlet.*; 14 | import jakarta.servlet.http.HttpServletRequest; 15 | import jakarta.servlet.http.HttpServletResponse; 16 | import java.io.IOException; 17 | import java.io.OutputStream; 18 | import java.nio.charset.StandardCharsets; 19 | import java.util.Arrays; 20 | 21 | @Component 22 | public class DynamicResponseFilter implements Filter { 23 | 24 | public static final String[] EXCLUDE_URL_LIST = new String[]{"csrf", "actuator", "v2/api-docs", "swagger", "springfox", "favicon.ico"}; 25 | private static final String MOCK_RESPONSE_FIELD = "mockServerResponseData"; 26 | private final Logger logger = LoggerFactory.getLogger(DynamicResponseFilter.class); 27 | 28 | @Override 29 | public void doFilter(ServletRequest request, 30 | ServletResponse response, 31 | FilterChain chain) 32 | throws IOException, ServletException { 33 | 34 | final HttpServletRequest httpServletRequest = (HttpServletRequest) request; 35 | final String path = httpServletRequest.getRequestURI(); 36 | if (!Arrays.stream(EXCLUDE_URL_LIST).anyMatch(path::contains) && !path.equals("/")) { 37 | if (request.getContentType() != null && 38 | (request.getContentType().equals(MediaType.APPLICATION_XML_VALUE) || request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE))) { 39 | parseResponseFromRequestAndFlushAsResponse(httpServletRequest, response, request.getContentType(), chain); 40 | return; 41 | } 42 | } 43 | chain.doFilter(httpServletRequest, response); 44 | } 45 | 46 | private void parseResponseFromRequestAndFlushAsResponse( 47 | final HttpServletRequest httpServletRequest, 48 | final ServletResponse response, 49 | final String contentType, 50 | final FilterChain chain) 51 | throws IOException, ServletException { 52 | 53 | final RequestWrapper requestWrapper = new RequestWrapper(httpServletRequest); 54 | final String body = requestWrapper.getBody(); 55 | 56 | String newResponse = null; 57 | try { 58 | if (contentType.equals(MediaType.APPLICATION_XML_VALUE)) { 59 | newResponse = parseResponseFromXML(body); 60 | } else if (contentType.equals(MediaType.APPLICATION_JSON_VALUE)) { 61 | newResponse = parseResponseFromJSON(body); 62 | } 63 | } catch (JsonProcessingException e) { 64 | e.printStackTrace(); 65 | throw e; 66 | } 67 | if (newResponse == null) { 68 | // this case means that the request body does not contain the MOCK_RESPONSE_FIELD. 69 | chain.doFilter(requestWrapper, response); 70 | } else { 71 | sendResponse(newResponse, response, MediaType.APPLICATION_XML_VALUE); 72 | } 73 | } 74 | 75 | private String parseResponseFromJSON(final String json) throws JsonProcessingException { 76 | ObjectMapper objectMapper = new ObjectMapper(); 77 | JsonNode rootJsonNode = objectMapper.readTree(json); 78 | JsonNode jsonNode = rootJsonNode.get(MOCK_RESPONSE_FIELD); 79 | if (jsonNode != null && !jsonNode.isMissingNode()) { 80 | return jsonNode.toPrettyString(); 81 | } 82 | return null; 83 | } 84 | 85 | private String parseResponseFromXML(final String xml) throws JsonProcessingException { 86 | XmlMapper objectMapper = new XmlMapper(); 87 | TreeNode rootXmlNode = objectMapper.readTree(xml); 88 | TreeNode xmlNode = rootXmlNode.get(MOCK_RESPONSE_FIELD); 89 | if (xmlNode != null && !xmlNode.isMissingNode()) { 90 | return xmlNode.toString(); 91 | } 92 | return null; 93 | } 94 | 95 | private void sendResponse(final String responseBodyStr, final ServletResponse servletResponse, final String mediaType) 96 | throws IOException { 97 | 98 | final byte[] responseBody = responseBodyStr.getBytes(StandardCharsets.UTF_8); 99 | final HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse; 100 | httpServletResponse.setContentLength(responseBody.length); 101 | httpServletResponse.setCharacterEncoding(StandardCharsets.UTF_8.toString()); 102 | httpServletResponse.setContentType(mediaType); 103 | final OutputStream outputStream = httpServletResponse.getOutputStream(); 104 | outputStream.write(responseBody); 105 | outputStream.flush(); 106 | outputStream.close(); 107 | } 108 | } -------------------------------------------------------------------------------- /src/main/java/com/demo/mockservice/filter/RequestWrapper.java: -------------------------------------------------------------------------------- 1 | package com.demo.mockservice.filter; 2 | 3 | import jakarta.servlet.ReadListener; 4 | import jakarta.servlet.ServletInputStream; 5 | import jakarta.servlet.http.HttpServletRequest; 6 | import jakarta.servlet.http.HttpServletRequestWrapper; 7 | import java.io.*; 8 | 9 | public class RequestWrapper extends HttpServletRequestWrapper { 10 | private final String body; 11 | 12 | public RequestWrapper(HttpServletRequest request) throws IOException { 13 | super(request); //So that other request method behave just like before 14 | 15 | StringBuilder stringBuilder = new StringBuilder(); 16 | BufferedReader bufferedReader = null; 17 | try { 18 | InputStream inputStream = request.getInputStream(); 19 | if (inputStream != null) { 20 | bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); 21 | char[] charBuffer = new char[128]; 22 | int bytesRead = -1; 23 | while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { 24 | stringBuilder.append(charBuffer, 0, bytesRead); 25 | } 26 | } else { 27 | stringBuilder.append(""); 28 | } 29 | } catch (IOException ex) { 30 | throw ex; 31 | } finally { 32 | if (bufferedReader != null) { 33 | try { 34 | bufferedReader.close(); 35 | } catch (IOException ex) { 36 | throw ex; 37 | } 38 | } 39 | } 40 | 41 | body = stringBuilder.toString(); 42 | } 43 | 44 | @Override 45 | public ServletInputStream getInputStream() throws IOException { 46 | final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes()); 47 | ServletInputStream servletInputStream = new ServletInputStream() { 48 | @Override 49 | public boolean isFinished() { 50 | return false; 51 | } 52 | 53 | @Override 54 | public boolean isReady() { 55 | return true; 56 | } 57 | 58 | @Override 59 | public void setReadListener(ReadListener readListener) { 60 | } 61 | 62 | public int read() throws IOException { 63 | return byteArrayInputStream.read(); 64 | } 65 | }; 66 | return servletInputStream; 67 | } 68 | 69 | @Override 70 | public BufferedReader getReader() throws IOException { 71 | return new BufferedReader(new InputStreamReader(this.getInputStream())); 72 | } 73 | 74 | //Use this method to read the request body N times 75 | public String getBody() { 76 | return this.body; 77 | } 78 | } -------------------------------------------------------------------------------- /src/main/java/com/demo/mockservice/util/FileReader.java: -------------------------------------------------------------------------------- 1 | package com.demo.mockservice.util; 2 | 3 | import com.demo.mockservice.App; 4 | import lombok.SneakyThrows; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.InputStream; 8 | import java.io.InputStreamReader; 9 | 10 | public class FileReader { 11 | 12 | @SneakyThrows 13 | public static String readFile(String fileName) { 14 | 15 | ClassLoader classLoader = App.class.getClassLoader(); 16 | InputStream inputStream = classLoader.getResourceAsStream("requests/" + fileName); 17 | 18 | StringBuilder resultStringBuilder = new StringBuilder(); 19 | try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) { 20 | String line; 21 | while ((line = br.readLine()) != null) { 22 | resultStringBuilder.append(line).append("\n"); 23 | } 24 | } 25 | return resultStringBuilder.toString(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/demo/mockservice/util/RandomDataGenerator.java: -------------------------------------------------------------------------------- 1 | package com.demo.mockservice.util; 2 | 3 | import org.jeasy.random.EasyRandom; 4 | 5 | public class RandomDataGenerator { 6 | 7 | private static final EasyRandom easyRandom = new EasyRandom(); 8 | 9 | public static T generateRandomData(Class clazz) { 10 | return easyRandom.nextObject(clazz); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8088 3 | 4 | spring: 5 | application: 6 | name: mock-server 7 | 8 | management: 9 | endpoints: 10 | web: 11 | exposure: 12 | include: "*" # enable all actuators which includes http-trace 13 | 14 | # log request and response to console output. 15 | logging.level.org.zalando.logbook: TRACE 16 | 17 | # below yml format should stay as is. Other yml formats does not work. possibly a bug. 18 | logbook.exclude[0]: "**/v2/api-docs" 19 | logbook.exclude[1]: "**/swagger-ui.html" 20 | logbook.exclude[2]: "**/webjars/**" 21 | logbook.exclude[3]: "**/swagger-resources/**" 22 | logbook.exclude[4]: "**/csrf" 23 | logbook.exclude[5]: "**/" 24 | logbook.exclude[6]: "/actuator**" 25 | logbook.exclude[7]: "/favicon.ico" -------------------------------------------------------------------------------- /src/main/resources/requests/response1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | DaVinci Code 4 | This book is from file 5 | 6 | -------------------------------------------------------------------------------- /start_jar.sh: -------------------------------------------------------------------------------- 1 | java -jar ./target/mock_data_server-0.0.1-SNAPSHOT.jar --------------------------------------------------------------------------------