├── moco-core └── src │ ├── test │ ├── resources │ │ ├── test │ │ │ ├── foo.bar │ │ │ └── dir.response │ │ ├── foo.request │ │ ├── foo.response │ │ ├── foo.template │ │ ├── bar.xml │ │ ├── foo.xml │ │ ├── foobar.xml │ │ ├── failover.response │ │ └── many_content_failover.response │ └── java │ │ └── com │ │ └── github │ │ └── dreamhead │ │ └── moco │ │ ├── RemoteTestUtils.java │ │ ├── AbstractMocoTest.java │ │ ├── internal │ │ └── MocoHttpServerTest.java │ │ ├── util │ │ └── FileContentTypeTest.java │ │ ├── MocoDefenseTest.java │ │ ├── mount │ │ └── MountToTest.java │ │ ├── extractor │ │ ├── UriRequestExtractorTest.java │ │ ├── VersionExtractorTest.java │ │ ├── HttpMethodExtractorTest.java │ │ └── ParamRequestExtractorTest.java │ │ ├── helper │ │ └── MocoTestHelper.java │ │ ├── MocoWebTest.java │ │ ├── MocoJsonTest.java │ │ ├── handler │ │ └── failover │ │ │ └── DefaultRequestTest.java │ │ ├── MocoFileRootTest.java │ │ ├── MocoXmlTest.java │ │ ├── MocoMountTest.java │ │ └── MocoContextTest.java │ └── main │ ├── java │ └── com │ │ └── github │ │ └── dreamhead │ │ └── moco │ │ ├── Runnable.java │ │ ├── dumper │ │ ├── Dumper.java │ │ ├── HttpMessageBaseDumper.java │ │ ├── HttpResponseDumper.java │ │ └── HttpRequestDumper.java │ │ ├── resource │ │ ├── Identifiable.java │ │ ├── Content.java │ │ ├── ResourceReader.java │ │ ├── ResourceConfigApplier.java │ │ ├── reader │ │ │ ├── ContentResourceReader.java │ │ │ ├── FileResourceReader.java │ │ │ ├── ClasspathFileResourceReader.java │ │ │ └── TemplateResourceReader.java │ │ ├── IdFactory.java │ │ ├── ContentResource.java │ │ └── Resource.java │ │ ├── ConfigApplier.java │ │ ├── MocoConfig.java │ │ ├── mount │ │ ├── MountPredicate.java │ │ ├── MountPathExtractor.java │ │ ├── MountTo.java │ │ ├── MountMatcher.java │ │ └── MountHandler.java │ │ ├── RequestExtractor.java │ │ ├── RequestMatcher.java │ │ ├── Setting.java │ │ ├── model │ │ ├── Request.java │ │ ├── Response.java │ │ ├── Session.java │ │ ├── DefaultRequest.java │ │ ├── MessageFactory.java │ │ └── Message.java │ │ ├── ResponseHandler.java │ │ ├── extractor │ │ ├── VersionExtractor.java │ │ ├── HttpMethodExtractor.java │ │ ├── ContentRequestExtractor.java │ │ ├── UriRequestExtractor.java │ │ ├── XmlExtractorHelper.java │ │ ├── HeaderRequestExtractor.java │ │ ├── CookieRequestExtractor.java │ │ ├── ParamRequestExtractor.java │ │ ├── JsonPathRequestExtractor.java │ │ ├── Extractors.java │ │ ├── FormRequestExtractor.java │ │ └── XPathRequestExtractor.java │ │ ├── handler │ │ ├── ContentTypeDetector.java │ │ ├── StatusCodeResponseHandler.java │ │ ├── failover │ │ │ └── Failover.java │ │ ├── LatencyResponseHandler.java │ │ ├── VersionResponseHandler.java │ │ ├── ContentHandler.java │ │ ├── AbstractContentResponseHandler.java │ │ ├── HeaderResponseHandler.java │ │ ├── AndResponseHandler.java │ │ ├── ResponseHandlers.java │ │ └── SequenceContentHandler.java │ │ ├── Runner.java │ │ ├── config │ │ ├── MocoContextConfig.java │ │ └── MocoFileRootConfig.java │ │ ├── util │ │ ├── Configs.java │ │ ├── Cookies.java │ │ └── FileContentType.java │ │ ├── matcher │ │ ├── MatchMatcher.java │ │ ├── AndRequestMatcher.java │ │ ├── OrRequestMatcher.java │ │ ├── EqRequestMatcher.java │ │ ├── CompositeRequestMatcher.java │ │ ├── AbstractOperatorMatcher.java │ │ └── JsonRequestMatcher.java │ │ ├── MocoMount.java │ │ ├── internal │ │ ├── MocoClient.java │ │ ├── MocoEventListener.java │ │ ├── MocoHttpServer.java │ │ ├── MocoServer.java │ │ └── MocoHandler.java │ │ ├── ResponseSetting.java │ │ ├── HttpServer.java │ │ └── setting │ │ └── BaseSetting.java │ └── resources │ └── logback.xml ├── moco-runner └── src │ ├── test │ ├── resources │ │ ├── foo.request │ │ ├── mount │ │ │ ├── foo.bar │ │ │ └── mount.response │ │ ├── foo.response │ │ ├── template.foo │ │ ├── path.request │ │ ├── path.response │ │ ├── xml.xml │ │ ├── any_response_with_file.json │ │ ├── multiple │ │ │ ├── fileroot-settings.json │ │ │ ├── settings.json │ │ │ ├── bar.json │ │ │ ├── foo.json │ │ │ ├── fileroot.json │ │ │ ├── context-settings.json │ │ │ └── env-settings.json │ │ ├── get_method.json │ │ ├── put_method.json │ │ ├── post_method.json │ │ ├── delete_method.json │ │ ├── form.json │ │ ├── query.json │ │ ├── json.json │ │ ├── jsonpath.json │ │ ├── header.json │ │ ├── xpath.json │ │ ├── redirect.json │ │ ├── path_resource.json │ │ ├── standalone_failover.json │ │ ├── xml.json │ │ ├── mount.json │ │ ├── proxy.json │ │ ├── match.json │ │ └── template.json │ └── java │ │ └── com │ │ └── github │ │ └── dreamhead │ │ └── moco │ │ ├── MocoRedirectStandaloneTest.java │ │ ├── MocoMulipleConfigurationsTest.java │ │ ├── MocoProxyStandaloneTest.java │ │ ├── MocoJsonStandaloneTest.java │ │ ├── bootstrap │ │ ├── ShutdownArgsTest.java │ │ └── StartArgsTest.java │ │ ├── MocoPathResourceTest.java │ │ ├── AbstractMocoStandaloneTest.java │ │ ├── runner │ │ ├── DynamicConfigurationRunnerTest.java │ │ ├── AbstractRunnerTest.java │ │ ├── DynamicSettingRunnerTest.java │ │ └── ShutdownMonitorRunnerTest.java │ │ ├── MocoXmlStandaloneTest.java │ │ ├── MocoMountStandaloneTest.java │ │ ├── MocoMatchTest.java │ │ ├── MocoTemplateStandaloneTest.java │ │ └── parser │ │ └── GlobalSettingParserTest.java │ └── main │ ├── resources │ └── favicon.png │ └── java │ └── com │ └── github │ └── dreamhead │ └── moco │ ├── runner │ ├── Runner.java │ ├── monitor │ │ ├── ShutdownListener.java │ │ ├── Monitor.java │ │ ├── FilesMonitor.java │ │ ├── MonitorFactory.java │ │ ├── FileMonitor.java │ │ └── ShutdownMonitor.java │ ├── MonitorRunner.java │ ├── RunnerSetting.java │ ├── StandaloneRunner.java │ ├── FileRunner.java │ ├── RunnerFactory.java │ └── SettingRunner.java │ ├── bootstrap │ ├── BootstrapTask.java │ ├── Main.java │ ├── ParseArgException.java │ ├── ShutdownArgs.java │ ├── ShutdownPortOption.java │ ├── StartTask.java │ ├── Bootstrap.java │ └── ShutdownTask.java │ └── parser │ ├── RequestMatcherFactory.java │ ├── model │ ├── GlobalSetting.java │ ├── ProxyContainer.java │ ├── TextContainer.java │ ├── TextContainerDeserializer.java │ ├── RequestSetting.java │ ├── MountSetting.java │ ├── ProxyContainerDeserializer.java │ ├── SessionSetting.java │ └── Dynamics.java │ ├── GlobalSettingParser.java │ └── HttpServerParser.java ├── moco-doc ├── configurations.md ├── moco.png ├── DukeChoice-960x90-lm.png ├── plan.md └── ReleaseNote.md ├── settings.gradle ├── .gitignore ├── .travis.yml ├── gradle.properties ├── MIT-LICENSE.txt └── README.md /moco-core/src/test/resources/test/foo.bar: -------------------------------------------------------------------------------- 1 | foo.bar -------------------------------------------------------------------------------- /moco-core/src/test/resources/foo.request: -------------------------------------------------------------------------------- 1 | foo.request -------------------------------------------------------------------------------- /moco-core/src/test/resources/foo.response: -------------------------------------------------------------------------------- 1 | foo.response -------------------------------------------------------------------------------- /moco-runner/src/test/resources/foo.request: -------------------------------------------------------------------------------- 1 | foo.request -------------------------------------------------------------------------------- /moco-runner/src/test/resources/mount/foo.bar: -------------------------------------------------------------------------------- 1 | foo.bar -------------------------------------------------------------------------------- /moco-core/src/test/resources/foo.template: -------------------------------------------------------------------------------- 1 | ${req.method} -------------------------------------------------------------------------------- /moco-runner/src/test/resources/foo.response: -------------------------------------------------------------------------------- 1 | foo.response -------------------------------------------------------------------------------- /moco-runner/src/test/resources/template.foo: -------------------------------------------------------------------------------- 1 | ${req.method} -------------------------------------------------------------------------------- /moco-core/src/test/resources/test/dir.response: -------------------------------------------------------------------------------- 1 | response from dir -------------------------------------------------------------------------------- /moco-runner/src/test/resources/path.request: -------------------------------------------------------------------------------- 1 | request from path -------------------------------------------------------------------------------- /moco-runner/src/test/resources/path.response: -------------------------------------------------------------------------------- 1 | response from path -------------------------------------------------------------------------------- /moco-doc/configurations.md: -------------------------------------------------------------------------------- 1 | moved to [APIs](/moco-doc/apis.md) 2 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/mount/mount.response: -------------------------------------------------------------------------------- 1 | response from mount -------------------------------------------------------------------------------- /moco-doc/moco.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/moco/master/moco-doc/moco.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'moco' 2 | include 'moco-core', 'moco-runner' 3 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/xml.xml: -------------------------------------------------------------------------------- 1 | 1 -------------------------------------------------------------------------------- /moco-doc/DukeChoice-960x90-lm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/moco/master/moco-doc/DukeChoice-960x90-lm.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | target 3 | build 4 | *.iml 5 | *.ipr 6 | *.iws 7 | out 8 | reports 9 | .DS_Store 10 | .idea 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk7 4 | - openjdk7 5 | - openjdk6 6 | 7 | script: 8 | "gradle check -i" -------------------------------------------------------------------------------- /moco-core/src/test/resources/bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2 4 | 5 | -------------------------------------------------------------------------------- /moco-core/src/test/resources/foo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | -------------------------------------------------------------------------------- /moco-runner/src/main/resources/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lite/moco/master/moco-runner/src/main/resources/favicon.png -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Default config properties, can be overridden by ~/.gradle/gradle.properties 2 | sonatypeUsername= 3 | sonatypePassword= 4 | -------------------------------------------------------------------------------- /moco-core/src/test/resources/foobar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 2 5 | 6 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/Runnable.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | public interface Runnable { 4 | void run() throws Exception; 5 | } 6 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/dumper/Dumper.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.dumper; 2 | 3 | public interface Dumper { 4 | String dump(T message); 5 | } 6 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/resource/Identifiable.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.resource; 2 | 3 | public interface Identifiable { 4 | String id(); 5 | } 6 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/any_response_with_file.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "response" : 4 | { 5 | "file" : "src/test/resources/foo.response" 6 | } 7 | } 8 | ] -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/resource/Content.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.resource; 2 | 3 | public interface Content { 4 | String getContentType(); 5 | } 6 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/runner/Runner.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.runner; 2 | 3 | public interface Runner { 4 | void run(); 5 | void stop(); 6 | } 7 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/ConfigApplier.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | public interface ConfigApplier { 4 | T apply(final MocoConfig config); 5 | } 6 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/bootstrap/BootstrapTask.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.bootstrap; 2 | 3 | public interface BootstrapTask { 4 | void run(String[] args); 5 | } 6 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/MocoConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | public interface MocoConfig { 4 | boolean isFor(String id); 5 | String apply(String target); 6 | } 7 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/runner/monitor/ShutdownListener.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.runner.monitor; 2 | 3 | public interface ShutdownListener { 4 | void onShutdown(); 5 | } 6 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/multiple/fileroot-settings.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "context": "/fileroot", 4 | "file_root": "src/test/resources/", 5 | "include": "multiple/fileroot.json" 6 | } 7 | ] -------------------------------------------------------------------------------- /moco-runner/src/test/resources/multiple/settings.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "include" : "src/test/resources/multiple/foo.json" 4 | }, 5 | { 6 | "include" : "src/test/resources/multiple/bar.json" 7 | } 8 | ] -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/runner/monitor/Monitor.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.runner.monitor; 2 | 3 | public interface Monitor { 4 | void startMonitor(); 5 | void stopMonitor(); 6 | } 7 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/multiple/bar.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request" : { 4 | "uri" : "/bar" 5 | }, 6 | "response" : { 7 | "text" : "bar" 8 | } 9 | } 10 | ] -------------------------------------------------------------------------------- /moco-runner/src/test/resources/multiple/foo.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request" : { 4 | "uri" : "/foo" 5 | }, 6 | "response" : { 7 | "text" : "foo" 8 | } 9 | } 10 | ] -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/mount/MountPredicate.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.mount; 2 | 3 | import com.google.common.base.Predicate; 4 | 5 | public interface MountPredicate extends Predicate { 6 | } 7 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/multiple/fileroot.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request" : { 4 | "uri" : "/fileroot" 5 | }, 6 | "response" : { 7 | "file" : "foo.response" 8 | } 9 | } 10 | ] -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/bootstrap/Main.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.bootstrap; 2 | 3 | public class Main { 4 | public static void main(String[] args) { 5 | new Bootstrap().run(args); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/RequestExtractor.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import io.netty.handler.codec.http.FullHttpRequest; 4 | 5 | public interface RequestExtractor { 6 | T extract(FullHttpRequest request); 7 | } 8 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/resource/ResourceReader.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.resource; 2 | 3 | import io.netty.handler.codec.http.FullHttpRequest; 4 | 5 | public interface ResourceReader { 6 | byte[] readFor(FullHttpRequest request); 7 | } 8 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/get_method.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request" : 4 | { 5 | "method" : "get", 6 | "uri" : "/get" 7 | }, 8 | "response" : 9 | { 10 | "text" : "response_for_get_method" 11 | } 12 | } 13 | ] 14 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/put_method.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request" : 4 | { 5 | "method" : "put", 6 | "uri" : "/put" 7 | }, 8 | "response" : 9 | { 10 | "text" : "response_for_put_method" 11 | } 12 | } 13 | ] 14 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/post_method.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request" : 4 | { 5 | "method" : "post", 6 | "uri" : "/post" 7 | }, 8 | "response" : 9 | { 10 | "text" : "response_for_post_method" 11 | } 12 | } 13 | ] 14 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/delete_method.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request" : 4 | { 5 | "method" : "delete", 6 | "uri" : "/delete" 7 | }, 8 | "response" : 9 | { 10 | "text" : "response_for_delete_method" 11 | } 12 | } 13 | ] 14 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/multiple/context-settings.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "context": "/foo", 4 | "include": "src/test/resources/multiple/foo.json" 5 | }, 6 | { 7 | "context": "/bar", 8 | "include": "src/test/resources/multiple/bar.json" 9 | } 10 | ] -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/RequestMatcher.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import io.netty.handler.codec.http.FullHttpRequest; 4 | 5 | public interface RequestMatcher extends ConfigApplier { 6 | boolean match(final FullHttpRequest request); 7 | } 8 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/resource/ResourceConfigApplier.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.resource; 2 | 3 | import com.github.dreamhead.moco.MocoConfig; 4 | 5 | public interface ResourceConfigApplier { 6 | public Resource apply(MocoConfig config, Resource resource); 7 | } 8 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/form.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request" : { 4 | "method" : "post", 5 | "forms" : { 6 | "name" : "dreamhead" 7 | } 8 | }, 9 | "response" : { 10 | "text" : "foobar" 11 | } 12 | } 13 | ] -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/Setting.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | public abstract class Setting extends ResponseSetting { 4 | protected final RequestMatcher matcher; 5 | 6 | protected Setting(RequestMatcher matcher) { 7 | this.matcher = matcher; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/query.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request" : 4 | { 5 | "uri" : "/query", 6 | "queries" : { 7 | "param" : "foo" 8 | } 9 | }, 10 | "response" : 11 | { 12 | "text" : "response_for_query_request" 13 | } 14 | } 15 | ] -------------------------------------------------------------------------------- /moco-runner/src/test/resources/json.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "uri": "/json", 5 | "text": { 6 | "json": "{\"foo\":\"bar\"}" 7 | } 8 | }, 9 | "response": { 10 | "text": "response_for_json_request" 11 | } 12 | } 13 | ] 14 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/jsonpath.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "uri": "/jsonpath", 5 | "json_paths": { 6 | "$.book[*].price": "1" 7 | } 8 | }, 9 | "response": { 10 | "text": "response_for_json_path_request" 11 | } 12 | } 13 | ] -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/resource/reader/ContentResourceReader.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.resource.reader; 2 | 3 | import com.github.dreamhead.moco.resource.Content; 4 | import com.github.dreamhead.moco.resource.ResourceReader; 5 | 6 | public interface ContentResourceReader extends ResourceReader, Content { 7 | } 8 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/multiple/env-settings.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "context": "/foo", 4 | "env": "foo", 5 | "include": "src/test/resources/multiple/foo.json" 6 | }, 7 | { 8 | "context": "/bar", 9 | "env": "bar", 10 | "include": "src/test/resources/multiple/bar.json" 11 | } 12 | ] -------------------------------------------------------------------------------- /moco-runner/src/test/resources/header.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request" : 4 | { 5 | "uri" : "/header", 6 | "headers" : { 7 | "content-type" : "application/json" 8 | } 9 | }, 10 | "response" : 11 | { 12 | "text" : "response_for_header_request" 13 | } 14 | } 15 | ] 16 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/xpath.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request" : 4 | { 5 | "uri" : "/xpath", 6 | "xpaths" : { 7 | "/request/parameters/id/text()" : "1" 8 | } 9 | }, 10 | "response" : 11 | { 12 | "text" : "response_for_xpath_request" 13 | } 14 | } 15 | ] 16 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/redirect.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request" : 4 | { 5 | "uri" : "/redirectTo" 6 | }, 7 | "response" : 8 | { 9 | "text" : "foo" 10 | } 11 | }, 12 | { 13 | "request" : 14 | { 15 | "uri" : "/redirect" 16 | }, 17 | 18 | "redirectTo" : "http://localhost:12306/redirectTo" 19 | } 20 | ] -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/parser/RequestMatcherFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.parser; 2 | 3 | import com.github.dreamhead.moco.RequestMatcher; 4 | import com.github.dreamhead.moco.parser.model.RequestSetting; 5 | 6 | public interface RequestMatcherFactory { 7 | RequestMatcher createRequestMatcher(RequestSetting request); 8 | } 9 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/model/Request.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.model; 2 | 3 | import java.util.Map; 4 | 5 | public interface Request { 6 | Map getQueries(); 7 | 8 | String getMethod(); 9 | 10 | String getVersion(); 11 | 12 | String getContent(); 13 | 14 | Map getHeaders(); 15 | } 16 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/ResponseHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import io.netty.handler.codec.http.FullHttpRequest; 4 | import io.netty.handler.codec.http.FullHttpResponse; 5 | 6 | public interface ResponseHandler extends ConfigApplier { 7 | void writeToResponse(FullHttpRequest request, FullHttpResponse response); 8 | } 9 | -------------------------------------------------------------------------------- /moco-core/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{dd MMM yyyy HH:mm:ss} [%thread] %-5level %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/bootstrap/ParseArgException.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.bootstrap; 2 | 3 | public class ParseArgException extends RuntimeException { 4 | public ParseArgException(String s) { 5 | super(s); 6 | } 7 | 8 | public ParseArgException(String s, Throwable throwable) { 9 | super(s, throwable); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/resource/IdFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.resource; 2 | 3 | public class IdFactory { 4 | public static Identifiable id(final String id) { 5 | return new Identifiable() { 6 | @Override 7 | public String id() { 8 | return id; 9 | } 10 | }; 11 | } 12 | 13 | private IdFactory() {} 14 | } 15 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/path_resource.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "uri": "/path-resource" 5 | }, 6 | "response": { 7 | "path_resource": "path.response" 8 | } 9 | }, 10 | { 11 | "request": { 12 | "path_resource": "path.request" 13 | }, 14 | "response": { 15 | "text": "path resource" 16 | } 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/extractor/VersionExtractor.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.extractor; 2 | 3 | import com.github.dreamhead.moco.RequestExtractor; 4 | import io.netty.handler.codec.http.FullHttpRequest; 5 | 6 | public class VersionExtractor implements RequestExtractor { 7 | @Override 8 | public String extract(FullHttpRequest request) { 9 | return request.getProtocolVersion().toString(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/extractor/HttpMethodExtractor.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.extractor; 2 | 3 | import com.github.dreamhead.moco.RequestExtractor; 4 | import io.netty.handler.codec.http.FullHttpRequest; 5 | 6 | public class HttpMethodExtractor implements RequestExtractor { 7 | @Override 8 | public String extract(FullHttpRequest request) { 9 | return request.getMethod().name().toUpperCase(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/standalone_failover.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "request" : { 3 | "version" : "HTTP/1.1", 4 | "headers" : { 5 | "Host" : "localhost:12306" 6 | } 7 | }, 8 | "response" : { 9 | "version" : "HTTP/1.1", 10 | "content" : "proxy", 11 | "headers" : { 12 | "Content-Length" : "5", 13 | "Content-Type" : "text/plain; charset=UTF-8" 14 | }, 15 | "statusCode" : 200 16 | } 17 | } ] -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/handler/ContentTypeDetector.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.handler; 2 | 3 | import io.netty.handler.codec.http.HttpResponse; 4 | 5 | public class ContentTypeDetector { 6 | public boolean hasContentType(HttpResponse response) { 7 | return hasHeader(response, "Content-Type"); 8 | } 9 | 10 | public boolean hasHeader(HttpResponse response, String headerName) { 11 | return response.headers().contains(headerName); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/model/Response.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 4 | 5 | @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) 6 | public class Response extends Message { 7 | private int statusCode; 8 | 9 | public void setStatusCode(int statusCode) { 10 | this.statusCode = statusCode; 11 | } 12 | 13 | public int getStatusCode() { 14 | return statusCode; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/extractor/ContentRequestExtractor.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.extractor; 2 | 3 | import com.github.dreamhead.moco.RequestExtractor; 4 | import io.netty.handler.codec.http.FullHttpRequest; 5 | 6 | import java.nio.charset.Charset; 7 | 8 | public class ContentRequestExtractor implements RequestExtractor { 9 | @Override 10 | public String extract(FullHttpRequest request) { 11 | return request.content().toString(Charset.defaultCharset()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /moco-core/src/test/java/com/github/dreamhead/moco/RemoteTestUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | public class RemoteTestUtils { 4 | private static final int PORT = 12306; 5 | private static final String BASE_URL = "http://localhost:" + PORT; 6 | 7 | public static int port() { 8 | return PORT; 9 | } 10 | 11 | public static String root() { 12 | return BASE_URL; 13 | } 14 | 15 | public static String remoteUrl(String uri) { 16 | return root() + uri; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/extractor/UriRequestExtractor.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.extractor; 2 | 3 | import com.github.dreamhead.moco.RequestExtractor; 4 | import io.netty.handler.codec.http.FullHttpRequest; 5 | import io.netty.handler.codec.http.QueryStringDecoder; 6 | 7 | public class UriRequestExtractor implements RequestExtractor { 8 | @Override 9 | public String extract(FullHttpRequest request) { 10 | return new QueryStringDecoder(request.getUri()).path(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /moco-doc/plan.md: -------------------------------------------------------------------------------- 1 | # Functionality 2 | 3 | ## Configuration 4 | 5 | ### Matcher 6 | * Marshal support 7 | 8 | ### Handler 9 | * Template function 10 | 11 | ## More 12 | * Async 13 | * REST 14 | * HTTPS 15 | 16 | ## Client 17 | * Socket 18 | 19 | # Document 20 | * Cookbook 21 | 22 | # Tools Support 23 | * JUnit better support 24 | * Proguard to reduce uberjar size 25 | * homebrew 26 | * IntelliJ IDEA plugin 27 | * Eclipse plugin 28 | * Runtime configuration change 29 | 30 | # Polyglot 31 | * Groovy 32 | * Scala 33 | * Clojure 34 | * Node.js -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/extractor/XmlExtractorHelper.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.extractor; 2 | 3 | import com.github.dreamhead.moco.RequestExtractor; 4 | import io.netty.handler.codec.http.FullHttpRequest; 5 | import org.xml.sax.InputSource; 6 | 7 | import java.io.StringReader; 8 | 9 | public class XmlExtractorHelper { 10 | public InputSource extractAsInputSource(FullHttpRequest request, RequestExtractor extractor) { 11 | return new InputSource(new StringReader(extractor.extract(request))); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/resource/ContentResource.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.resource; 2 | 3 | import com.github.dreamhead.moco.resource.reader.ContentResourceReader; 4 | 5 | public class ContentResource extends Resource implements Content { 6 | public ContentResource(Identifiable identifiable, ResourceConfigApplier configApplier, ContentResourceReader reader) { 7 | super(identifiable, configApplier, reader); 8 | } 9 | 10 | public String getContentType() { 11 | return ((ContentResourceReader)reader).getContentType(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/Runner.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import com.github.dreamhead.moco.internal.ActualHttpServer; 4 | import com.github.dreamhead.moco.internal.MocoHttpServer; 5 | 6 | public class Runner { 7 | public static void running(HttpServer httpServer, Runnable runnable) throws Exception { 8 | MocoHttpServer server = new MocoHttpServer((ActualHttpServer)httpServer); 9 | try { 10 | server.start(); 11 | runnable.run(); 12 | } finally { 13 | server.stop(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/extractor/HeaderRequestExtractor.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.extractor; 2 | 3 | import com.github.dreamhead.moco.RequestExtractor; 4 | import io.netty.handler.codec.http.FullHttpRequest; 5 | 6 | public class HeaderRequestExtractor implements RequestExtractor { 7 | private final String name; 8 | 9 | public HeaderRequestExtractor(String name) { 10 | this.name = name; 11 | } 12 | 13 | @Override 14 | public String extract(FullHttpRequest request) { 15 | return request.headers().get(name); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/config/MocoContextConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.config; 2 | 3 | import com.github.dreamhead.moco.MocoConfig; 4 | 5 | public class MocoContextConfig implements MocoConfig { 6 | private final String context; 7 | 8 | public MocoContextConfig(String context) { 9 | this.context = context; 10 | } 11 | 12 | @Override 13 | public boolean isFor(String id) { 14 | return "uri".equalsIgnoreCase(id); 15 | } 16 | 17 | @Override 18 | public String apply(String uri) { 19 | return context + uri; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /moco-core/src/test/java/com/github/dreamhead/moco/AbstractMocoTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import com.github.dreamhead.moco.helper.MocoTestHelper; 4 | import org.junit.Before; 5 | 6 | import static com.github.dreamhead.moco.Moco.httpserver; 7 | import static com.github.dreamhead.moco.RemoteTestUtils.port; 8 | 9 | public class AbstractMocoTest { 10 | protected HttpServer server; 11 | protected MocoTestHelper helper; 12 | 13 | @Before 14 | public void setUp() throws Exception { 15 | helper = new MocoTestHelper(); 16 | server = httpserver(port()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /moco-core/src/test/resources/failover.response: -------------------------------------------------------------------------------- 1 | [ { 2 | "request" : { 3 | "version" : "HTTP/1.1", 4 | "content" : "proxy", 5 | "headers" : { 6 | "Host" : "localhost:12306", 7 | "Content-Length" : "5", 8 | "User-Agent" : "Apache-HttpClient/4.2.5 (java 1.5)", 9 | "Connection" : "Keep-Alive" 10 | }, 11 | "method" : "POST" 12 | }, 13 | "response" : { 14 | "version" : "HTTP/1.1", 15 | "content" : "proxy", 16 | "headers" : { 17 | "Content-Length" : "5", 18 | "Content-Type" : "text/plain; charset=UTF-8" 19 | }, 20 | "statusCode" : 200 21 | } 22 | } ] -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/util/Configs.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.util; 2 | 3 | import com.github.dreamhead.moco.ConfigApplier; 4 | import com.github.dreamhead.moco.MocoConfig; 5 | 6 | public class Configs { 7 | public static > T configItem(T source, MocoConfig... configs) { 8 | if (source == null) { 9 | return null; 10 | } 11 | 12 | T target = source; 13 | for (MocoConfig config : configs) { 14 | target = target.apply(config); 15 | } 16 | return target; 17 | } 18 | 19 | private Configs() {} 20 | } 21 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/mount/MountPathExtractor.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.mount; 2 | 3 | import com.github.dreamhead.moco.extractor.UriRequestExtractor; 4 | import io.netty.handler.codec.http.FullHttpRequest; 5 | 6 | public class MountPathExtractor { 7 | private final MountTo target; 8 | private final UriRequestExtractor extractor = new UriRequestExtractor(); 9 | 10 | public MountPathExtractor(MountTo target) { 11 | this.target = target; 12 | } 13 | 14 | public String extract(FullHttpRequest request) { 15 | return target.extract(extractor.extract(request)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/xml.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "uri": "/xml", 5 | "text": { 6 | "xml": "1" 7 | } 8 | }, 9 | "response": { 10 | "text": "response_for_xml_request" 11 | } 12 | }, 13 | { 14 | "request": { 15 | "uri": "/xmlfile", 16 | "file": { 17 | "xml": "src/test/resources/xml.xml" 18 | } 19 | }, 20 | "response": { 21 | "text": "response_for_xml_file_request" 22 | } 23 | } 24 | ] 25 | -------------------------------------------------------------------------------- /moco-core/src/test/java/com/github/dreamhead/moco/internal/MocoHttpServerTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.internal; 2 | 3 | import org.junit.Test; 4 | 5 | public class MocoHttpServerTest { 6 | @Test 7 | public void should_stop_stoped_server_without_exception() { 8 | MocoHttpServer server = new MocoHttpServer(new ActualHttpServer(8080)); 9 | server.stop(); 10 | } 11 | 12 | @Test 13 | public void should_stop_server_many_times_without_exception() { 14 | MocoHttpServer server = new MocoHttpServer(new ActualHttpServer(8080)); 15 | server.start(); 16 | server.stop(); 17 | server.stop(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /moco-runner/src/test/java/com/github/dreamhead/moco/MocoRedirectStandaloneTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.IOException; 6 | 7 | import static com.github.dreamhead.moco.RemoteTestUtils.remoteUrl; 8 | import static org.hamcrest.CoreMatchers.is; 9 | import static org.junit.Assert.assertThat; 10 | 11 | public class MocoRedirectStandaloneTest extends AbstractMocoStandaloneTest { 12 | @Test 13 | public void should_redirect_to_expected_url() throws IOException { 14 | runWithConfiguration("redirect.json"); 15 | 16 | assertThat(helper.get(remoteUrl("/redirect")), is("foo")); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/config/MocoFileRootConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.config; 2 | 3 | import com.github.dreamhead.moco.MocoConfig; 4 | 5 | import java.io.File; 6 | 7 | public class MocoFileRootConfig implements MocoConfig { 8 | private final String fileRoot; 9 | 10 | public MocoFileRootConfig(String fileRoot) { 11 | this.fileRoot = fileRoot; 12 | } 13 | 14 | @Override 15 | public boolean isFor(String id) { 16 | return "file".equals(id); 17 | } 18 | 19 | @Override 20 | public String apply(String filename) { 21 | return this.fileRoot + File.separator + filename; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/runner/MonitorRunner.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.runner; 2 | 3 | import com.github.dreamhead.moco.runner.monitor.Monitor; 4 | 5 | public class MonitorRunner implements Runner { 6 | private final Runner runner; 7 | private final Monitor monitor; 8 | 9 | public MonitorRunner(Runner runner, Monitor monitor) { 10 | this.runner = runner; 11 | this.monitor = monitor; 12 | } 13 | 14 | @Override 15 | public void run() { 16 | this.runner.run(); 17 | this.monitor.startMonitor(); 18 | } 19 | 20 | @Override 21 | public void stop() { 22 | this.monitor.stopMonitor(); 23 | this.runner.stop(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/mount.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "mount" : 4 | { 5 | "dir" : "src/test/resources/mount", 6 | "uri" : "/mount" 7 | } 8 | }, 9 | { 10 | "mount" : 11 | { 12 | "dir" : "src/test/resources/mount", 13 | "uri" : "/mount-include", 14 | "includes" : 15 | [ 16 | "*.response" 17 | ] 18 | } 19 | }, 20 | { 21 | "mount" : 22 | { 23 | "dir" : "src/test/resources/mount", 24 | "uri" : "/mount-exclude", 25 | "excludes" : 26 | [ 27 | "*.response" 28 | ] 29 | } 30 | } 31 | ] 32 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/extractor/CookieRequestExtractor.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.extractor; 2 | 3 | import com.github.dreamhead.moco.RequestExtractor; 4 | import com.github.dreamhead.moco.util.Cookies; 5 | import io.netty.handler.codec.http.FullHttpRequest; 6 | 7 | public class CookieRequestExtractor implements RequestExtractor { 8 | private final Cookies cookies = new Cookies(); 9 | 10 | private final String key; 11 | 12 | public CookieRequestExtractor(String key) { 13 | this.key = key; 14 | } 15 | 16 | @Override 17 | public String extract(FullHttpRequest request) { 18 | return cookies.decodeCookie(request.headers().get("Cookie"), key); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /moco-runner/src/test/java/com/github/dreamhead/moco/MocoMulipleConfigurationsTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.IOException; 6 | 7 | import static com.github.dreamhead.moco.RemoteTestUtils.remoteUrl; 8 | import static org.hamcrest.CoreMatchers.is; 9 | import static org.junit.Assert.assertThat; 10 | 11 | public class MocoMulipleConfigurationsTest extends AbstractMocoStandaloneTest { 12 | @Test 13 | public void should_run_with_many_configurations() throws IOException { 14 | runWithConfiguration("multiple/foo.json", "multiple/bar.json"); 15 | assertThat(helper.get(remoteUrl("/foo")), is("foo")); 16 | assertThat(helper.get(remoteUrl("/bar")), is("bar")); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/parser/model/GlobalSetting.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.parser.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.io.File; 6 | 7 | public class GlobalSetting { 8 | private String include; 9 | private String context; 10 | @JsonProperty("file_root") 11 | private String fileRoot; 12 | private String env; 13 | 14 | public String getInclude() { 15 | return fileRoot != null ? new File(fileRoot, include).getPath() : include; 16 | } 17 | 18 | public String getContext() { 19 | return context; 20 | } 21 | 22 | public String getFileRoot() { 23 | return fileRoot; 24 | } 25 | 26 | public String getEnv() { 27 | return env; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/handler/StatusCodeResponseHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.handler; 2 | 3 | import com.github.dreamhead.moco.MocoConfig; 4 | import com.github.dreamhead.moco.ResponseHandler; 5 | import io.netty.handler.codec.http.*; 6 | 7 | public class StatusCodeResponseHandler implements ResponseHandler { 8 | private final HttpResponseStatus status; 9 | 10 | public StatusCodeResponseHandler(int code) { 11 | status = HttpResponseStatus.valueOf(code); 12 | } 13 | 14 | @Override 15 | public void writeToResponse(FullHttpRequest request, FullHttpResponse response) { 16 | response.setStatus(status); 17 | } 18 | 19 | @Override 20 | public ResponseHandler apply(final MocoConfig config) { 21 | return this; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/handler/failover/Failover.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.handler.failover; 2 | 3 | import io.netty.handler.codec.http.FullHttpRequest; 4 | import io.netty.handler.codec.http.FullHttpResponse; 5 | 6 | public interface Failover { 7 | void onCompleteResponse(FullHttpRequest request, FullHttpResponse response); 8 | void failover(FullHttpRequest request, FullHttpResponse response); 9 | 10 | Failover EMPTY_FAILOVER = new Failover() { 11 | @Override 12 | public void onCompleteResponse(FullHttpRequest request, FullHttpResponse response) { 13 | } 14 | 15 | @Override 16 | public void failover(FullHttpRequest request, FullHttpResponse response) { 17 | throw new RuntimeException("no failover response found"); 18 | } 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/extractor/ParamRequestExtractor.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.extractor; 2 | 3 | import com.github.dreamhead.moco.RequestExtractor; 4 | import io.netty.handler.codec.http.FullHttpRequest; 5 | import io.netty.handler.codec.http.QueryStringDecoder; 6 | 7 | import java.util.List; 8 | 9 | public class ParamRequestExtractor implements RequestExtractor { 10 | private final String param; 11 | 12 | public ParamRequestExtractor(String param) { 13 | this.param = param; 14 | } 15 | 16 | @Override 17 | public String extract(FullHttpRequest request) { 18 | QueryStringDecoder decoder = new QueryStringDecoder(request.getUri()); 19 | List values = decoder.parameters().get(param); 20 | return values == null ? null : values.get(0); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/parser/model/ProxyContainer.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.parser.model; 2 | 3 | import com.google.common.base.Objects; 4 | 5 | public class ProxyContainer { 6 | private final String url; 7 | private final String failover; 8 | 9 | public ProxyContainer(String url, String failover) { 10 | this.url = url; 11 | this.failover = failover; 12 | } 13 | 14 | public String getUrl() { 15 | return url; 16 | } 17 | 18 | public String getFailover() { 19 | return failover; 20 | } 21 | 22 | @Override 23 | public String toString() { 24 | return Objects.toStringHelper(ProxyContainer.class) 25 | .add("url", this.url) 26 | .add("failover", this.failover) 27 | .toString(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/proxy.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request" : 4 | { 5 | "uri" : "/target" 6 | }, 7 | "response" : 8 | { 9 | "text" : "proxy_target" 10 | } 11 | }, 12 | { 13 | "request" : 14 | { 15 | "uri" : "/proxy" 16 | }, 17 | "response" : 18 | { 19 | "proxy" : "http://localhost:12306/target" 20 | } 21 | }, 22 | { 23 | "request" : 24 | { 25 | "uri" : "/failover" 26 | }, 27 | "response" : 28 | { 29 | "proxy" : 30 | { 31 | "url" : "http://localhost:12306/unknown", 32 | "failover" : "src/test/resources/standalone_failover.json" 33 | } 34 | } 35 | } 36 | 37 | ] 38 | 39 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/model/Session.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.model; 2 | 3 | public class Session { 4 | private DefaultRequest request; 5 | private Response response; 6 | 7 | public DefaultRequest getRequest() { 8 | return request; 9 | } 10 | 11 | public void setRequest(DefaultRequest request) { 12 | this.request = request; 13 | } 14 | 15 | public Response getResponse() { 16 | return response; 17 | } 18 | 19 | public void setResponse(Response response) { 20 | this.response = response; 21 | } 22 | 23 | public static Session newSession(DefaultRequest request, Response response) { 24 | Session session = new Session(); 25 | session.setRequest(request); 26 | session.setResponse(response); 27 | return session; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /moco-core/src/test/java/com/github/dreamhead/moco/util/FileContentTypeTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.util; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.hamcrest.CoreMatchers.is; 6 | import static org.junit.Assert.assertThat; 7 | 8 | public class FileContentTypeTest { 9 | private final String DEFAULT_CONTENT_TYPE = "text/plain; charset=UTF-8"; 10 | private final String PNG_CONTENT_TYPE = "image/png"; 11 | 12 | @Test 13 | public void should_get_type_from_filename() { 14 | FileContentType contentType = new FileContentType("logo.png"); 15 | assertThat(contentType.getContentType(), is(PNG_CONTENT_TYPE)); 16 | } 17 | 18 | @Test 19 | public void should_get_default_type_from_unknown_name() { 20 | FileContentType contentType = new FileContentType("UNKNOWN_FILE"); 21 | assertThat(contentType.getContentType(), is(DEFAULT_CONTENT_TYPE)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/mount/MountTo.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.mount; 2 | 3 | import com.github.dreamhead.moco.MocoConfig; 4 | 5 | import static com.google.common.base.Strings.nullToEmpty; 6 | 7 | public class MountTo { 8 | private final String target; 9 | 10 | public MountTo(String target) { 11 | if (!target.endsWith("/")) { 12 | this.target = target + "/"; 13 | } else { 14 | this.target = target; 15 | } 16 | } 17 | 18 | public String extract(String uri) { 19 | return uri.startsWith(this.target) ? nullToEmpty(uri.replaceFirst(this.target, "")) : ""; 20 | } 21 | 22 | public MountTo apply(MocoConfig config) { 23 | if (config.isFor("uri")) { 24 | return new MountTo(config.apply(this.target)); 25 | } 26 | 27 | return null; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /moco-core/src/test/java/com/github/dreamhead/moco/MocoDefenseTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import org.apache.http.client.HttpResponseException; 4 | import org.junit.Test; 5 | 6 | import static com.github.dreamhead.moco.Moco.*; 7 | import static com.github.dreamhead.moco.RemoteTestUtils.root; 8 | import static com.github.dreamhead.moco.Runner.running; 9 | 10 | public class MocoDefenseTest extends AbstractMocoTest { 11 | @Test(expected = HttpResponseException.class) 12 | public void should_work_well_without_response_setting() throws Exception { 13 | server = httpserver(12306, context("/foo")); 14 | server.request(by("bar")); 15 | 16 | running(server, new Runnable() { 17 | @Override 18 | public void run() throws Exception { 19 | helper.postContent(root(), "bar"); 20 | } 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/runner/RunnerSetting.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.runner; 2 | 3 | import com.google.common.base.Optional; 4 | 5 | import java.io.InputStream; 6 | 7 | public class RunnerSetting { 8 | private InputStream stream; 9 | private final Optional context; 10 | private final Optional fileRoot; 11 | 12 | public RunnerSetting(InputStream stream, String context, String fileRoot) { 13 | this.stream = stream; 14 | this.context = Optional.fromNullable(context); 15 | this.fileRoot = Optional.fromNullable(fileRoot); 16 | } 17 | 18 | public InputStream getStream() { 19 | return stream; 20 | } 21 | 22 | public Optional getContext() { 23 | return context; 24 | } 25 | 26 | public Optional getFileRoot() { 27 | return fileRoot; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /moco-runner/src/test/java/com/github/dreamhead/moco/MocoProxyStandaloneTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.IOException; 6 | 7 | import static com.github.dreamhead.moco.RemoteTestUtils.remoteUrl; 8 | import static org.hamcrest.CoreMatchers.is; 9 | import static org.junit.Assert.assertThat; 10 | 11 | public class MocoProxyStandaloneTest extends AbstractMocoStandaloneTest { 12 | @Test 13 | public void should_response_with_proxy() throws IOException { 14 | runWithConfiguration("proxy.json"); 15 | String content = helper.get(remoteUrl("/proxy")); 16 | assertThat(content, is("proxy_target")); 17 | } 18 | 19 | @Test 20 | public void should_failover() throws IOException { 21 | runWithConfiguration("proxy.json"); 22 | String content = helper.postContent(remoteUrl("/failover"), "proxy"); 23 | assertThat(content, is("proxy")); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/dumper/HttpMessageBaseDumper.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.dumper; 2 | 3 | import com.google.common.base.Joiner; 4 | import io.netty.handler.codec.http.FullHttpMessage; 5 | import io.netty.handler.codec.http.HttpHeaders; 6 | import io.netty.util.internal.StringUtil; 7 | 8 | import java.nio.charset.Charset; 9 | 10 | public abstract class HttpMessageBaseDumper implements Dumper { 11 | protected final Joiner.MapJoiner headerJoiner = Joiner.on(StringUtil.NEWLINE).withKeyValueSeparator(": "); 12 | 13 | protected static void appendContent(FullHttpMessage message, StringBuilder buf) { 14 | long contentLength = HttpHeaders.getContentLength(message, -1); 15 | if (contentLength > 0) { 16 | buf.append(StringUtil.NEWLINE); 17 | buf.append(StringUtil.NEWLINE); 18 | buf.append(message.content().toString(Charset.defaultCharset())); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/handler/LatencyResponseHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.handler; 2 | 3 | import com.github.dreamhead.moco.MocoConfig; 4 | import com.github.dreamhead.moco.ResponseHandler; 5 | import io.netty.handler.codec.http.FullHttpRequest; 6 | import io.netty.handler.codec.http.FullHttpResponse; 7 | 8 | public class LatencyResponseHandler implements ResponseHandler { 9 | private final long millis; 10 | 11 | public LatencyResponseHandler(long millis) { 12 | this.millis = millis; 13 | } 14 | 15 | @Override 16 | public void writeToResponse(FullHttpRequest request, FullHttpResponse response) { 17 | try { 18 | Thread.sleep(millis); 19 | } catch (InterruptedException e) { 20 | throw new RuntimeException(e); 21 | } 22 | } 23 | 24 | @Override 25 | public ResponseHandler apply(final MocoConfig config) { 26 | return this; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/bootstrap/ShutdownArgs.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.bootstrap; 2 | 3 | import org.apache.commons.cli.*; 4 | 5 | public class ShutdownArgs extends ShutdownPortOption { 6 | public ShutdownArgs(Integer shutdownPort) { 7 | super(shutdownPort); 8 | } 9 | 10 | public static ShutdownArgs parse(String[] args) { 11 | try { 12 | CommandLineParser parser = new PosixParser(); 13 | CommandLine cmd = parser.parse(createShutdownOptions(), args); 14 | return new ShutdownArgs(getShutdownPort(cmd.getOptionValue("s"))); 15 | } catch (ParseException e) { 16 | throw new ParseArgException("fail to parse arguments", e); 17 | } 18 | } 19 | 20 | private static Options createShutdownOptions() { 21 | Options options = new Options(); 22 | options.addOption(shutdownPortOption()); 23 | return options; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/parser/model/TextContainer.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.parser.model; 2 | 3 | import com.google.common.base.Objects; 4 | 5 | public class TextContainer { 6 | private final String text; 7 | private final String operation; 8 | 9 | public TextContainer(String text, String operation) { 10 | this.text = text; 11 | this.operation = operation; 12 | } 13 | 14 | public boolean isRawText() { 15 | return this.operation == null; 16 | } 17 | 18 | public String getText() { 19 | return text; 20 | } 21 | 22 | public String getOperation() { 23 | return operation; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return Objects.toStringHelper(this) 29 | .omitNullValues() 30 | .add("text", text) 31 | .add("operation", operation) 32 | .toString(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/resource/reader/FileResourceReader.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.resource.reader; 2 | 3 | import com.github.dreamhead.moco.util.FileContentType; 4 | import io.netty.handler.codec.http.FullHttpRequest; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | 9 | import static com.google.common.io.Files.toByteArray; 10 | 11 | public class FileResourceReader implements ContentResourceReader { 12 | private File file; 13 | 14 | public FileResourceReader(File file) { 15 | this.file = file; 16 | } 17 | 18 | @Override 19 | public String getContentType() { 20 | return new FileContentType(file.getName()).getContentType(); 21 | } 22 | 23 | @Override 24 | public byte[] readFor(FullHttpRequest request) { 25 | try { 26 | return toByteArray(file); 27 | } catch (IOException e) { 28 | throw new RuntimeException(e); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /moco-core/src/test/java/com/github/dreamhead/moco/mount/MountToTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.mount; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.hamcrest.CoreMatchers.is; 6 | import static org.hamcrest.Matchers.isEmptyOrNullString; 7 | import static org.junit.Assert.assertThat; 8 | 9 | public class MountToTest { 10 | 11 | @Test 12 | public void should_get_relative_path_from_uri() { 13 | MountTo to = new MountTo("/dir"); 14 | assertThat(to.extract("/dir/filename"), is("filename")); 15 | } 16 | 17 | @Test 18 | public void should_return_null_if_uri_does_not_match() { 19 | MountTo to = new MountTo("/dir"); 20 | assertThat(to.extract("/target/filename"), isEmptyOrNullString()); 21 | } 22 | 23 | @Test 24 | public void should_return_null_if_no_relative_path_found() { 25 | MountTo to = new MountTo("/dir"); 26 | assertThat(to.extract("/dir/"), isEmptyOrNullString()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/dumper/HttpResponseDumper.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.dumper; 2 | 3 | import io.netty.handler.codec.http.FullHttpResponse; 4 | import io.netty.handler.codec.http.HttpResponse; 5 | import io.netty.util.internal.StringUtil; 6 | 7 | public class HttpResponseDumper extends HttpMessageBaseDumper { 8 | @Override 9 | public String dump(FullHttpResponse response) { 10 | StringBuilder buf = new StringBuilder(); 11 | appendResponseProtocolLine(response, buf); 12 | buf.append(StringUtil.NEWLINE); 13 | headerJoiner.appendTo(buf, response.headers()); 14 | appendContent(response, buf); 15 | 16 | return buf.toString(); 17 | } 18 | 19 | private void appendResponseProtocolLine(HttpResponse response, StringBuilder buf) { 20 | buf.append(response.getProtocolVersion().text()); 21 | buf.append(' '); 22 | buf.append(response.getStatus().toString()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/runner/StandaloneRunner.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.runner; 2 | 3 | import com.github.dreamhead.moco.HttpServer; 4 | import com.github.dreamhead.moco.internal.ActualHttpServer; 5 | import com.github.dreamhead.moco.internal.MocoHttpServer; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | public class StandaloneRunner { 10 | private static Logger logger = LoggerFactory.getLogger(StandaloneRunner.class); 11 | 12 | private MocoHttpServer server; 13 | 14 | public void run(HttpServer httpServer) { 15 | ActualHttpServer actualHttpServer = (ActualHttpServer) httpServer; 16 | server = new MocoHttpServer(actualHttpServer); 17 | logger.info("Server is started at {}", actualHttpServer.getPort()); 18 | server.start(); 19 | } 20 | 21 | public void stop() { 22 | if (server != null) { 23 | server.stop(); 24 | logger.info("Server stopped."); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/dumper/HttpRequestDumper.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.dumper; 2 | 3 | import io.netty.handler.codec.http.FullHttpRequest; 4 | import io.netty.handler.codec.http.HttpRequest; 5 | import io.netty.util.internal.StringUtil; 6 | 7 | public class HttpRequestDumper extends HttpMessageBaseDumper { 8 | public String dump(FullHttpRequest request) { 9 | StringBuilder buf = new StringBuilder(); 10 | appendRequestProtocolLine(request, buf); 11 | buf.append(StringUtil.NEWLINE); 12 | headerJoiner.appendTo(buf, request.headers()); 13 | appendContent(request, buf); 14 | 15 | return buf.toString(); 16 | } 17 | 18 | private void appendRequestProtocolLine(HttpRequest request, StringBuilder buf) { 19 | buf.append(request.getMethod().toString()); 20 | buf.append(' '); 21 | buf.append(request.getUri()); 22 | buf.append(' '); 23 | buf.append(request.getProtocolVersion().text()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/matcher/MatchMatcher.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.matcher; 2 | 3 | import com.github.dreamhead.moco.MocoConfig; 4 | import com.github.dreamhead.moco.RequestExtractor; 5 | import com.github.dreamhead.moco.RequestMatcher; 6 | import com.github.dreamhead.moco.resource.Resource; 7 | import com.google.common.base.Predicate; 8 | 9 | import java.util.regex.Pattern; 10 | 11 | public class MatchMatcher extends AbstractOperatorMatcher { 12 | public MatchMatcher(final RequestExtractor extractor, final Resource expected) { 13 | super(extractor, new Predicate() { 14 | 15 | @Override 16 | public boolean apply(String input) { 17 | Pattern pattern = Pattern.compile(new String(expected.readFor(null))); 18 | return pattern.matcher(input).matches(); 19 | } 20 | }); 21 | } 22 | 23 | @Override 24 | public RequestMatcher apply(final MocoConfig config) { 25 | return this; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /moco-runner/src/test/java/com/github/dreamhead/moco/MocoJsonStandaloneTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.IOException; 6 | 7 | import static com.github.dreamhead.moco.RemoteTestUtils.remoteUrl; 8 | import static org.hamcrest.CoreMatchers.is; 9 | import static org.junit.Assert.assertThat; 10 | 11 | public class MocoJsonStandaloneTest extends AbstractMocoStandaloneTest { 12 | @Test 13 | public void should_return_expected_response_based_on_specified_xml_request() throws IOException { 14 | runWithConfiguration("json.json"); 15 | assertThat(helper.postContent(remoteUrl("/json"), "{\n\t\"foo\":\"bar\"\n}"), is("response_for_json_request")); 16 | } 17 | 18 | @Test 19 | public void should_return_expected_reponse_based_on_json_path_request() throws IOException { 20 | runWithConfiguration("jsonpath.json"); 21 | assertThat(helper.postContent(remoteUrl("/jsonpath"), "{\"book\":[{\"price\":\"1\"}]}"), is("response_for_json_path_request")); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /moco-runner/src/test/java/com/github/dreamhead/moco/bootstrap/ShutdownArgsTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.bootstrap; 2 | 3 | import static com.github.dreamhead.moco.bootstrap.ShutdownArgs.parse; 4 | import static org.hamcrest.CoreMatchers.is; 5 | import static org.junit.Assert.*; 6 | 7 | import org.junit.Test; 8 | 9 | public class ShutdownArgsTest { 10 | 11 | private final int DEFAULT_SHUTDOWN_PORT = 9527; 12 | 13 | @Test 14 | public void should_parse_shutdown_arguments() { 15 | ShutdownArgs args = parse(new String[] { "shutdown", "-s", "12305" }); 16 | assertThat(12305, is(args.getShutdownPort(DEFAULT_SHUTDOWN_PORT))); 17 | } 18 | 19 | @Test 20 | public void should_parse_shutdown_default_arguments() { 21 | ShutdownArgs args = parse(new String[] { "shutdown" }); 22 | assertThat(DEFAULT_SHUTDOWN_PORT, 23 | is(args.getShutdownPort(DEFAULT_SHUTDOWN_PORT))); 24 | } 25 | 26 | @Test(expected = ParseArgException.class) 27 | public void should_set_shutdown_port() { 28 | parse(new String[] { "shutdown", "-s" }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/resource/reader/ClasspathFileResourceReader.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.resource.reader; 2 | 3 | import com.github.dreamhead.moco.util.FileContentType; 4 | import io.netty.handler.codec.http.FullHttpRequest; 5 | 6 | import java.io.IOException; 7 | 8 | import static com.google.common.io.ByteStreams.toByteArray; 9 | 10 | public class ClasspathFileResourceReader implements ContentResourceReader { 11 | private String filename; 12 | 13 | public ClasspathFileResourceReader(String filename) { 14 | this.filename = filename; 15 | } 16 | 17 | @Override 18 | public byte[] readFor(FullHttpRequest request) { 19 | try { 20 | return toByteArray(this.getClass().getClassLoader().getResourceAsStream(filename)); 21 | } catch (IOException e) { 22 | throw new RuntimeException(e); 23 | } 24 | } 25 | 26 | @Override 27 | public String getContentType() { 28 | return new FileContentType(this.filename).getContentType(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/handler/VersionResponseHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.handler; 2 | 3 | import com.github.dreamhead.moco.MocoConfig; 4 | import com.github.dreamhead.moco.ResponseHandler; 5 | import com.github.dreamhead.moco.resource.Resource; 6 | import io.netty.handler.codec.http.FullHttpRequest; 7 | import io.netty.handler.codec.http.FullHttpResponse; 8 | import io.netty.handler.codec.http.HttpVersion; 9 | 10 | public class VersionResponseHandler implements ResponseHandler { 11 | private final Resource resource; 12 | 13 | public VersionResponseHandler(Resource resource) { 14 | this.resource = resource; 15 | } 16 | 17 | @Override 18 | public void writeToResponse(FullHttpRequest request, FullHttpResponse response) { 19 | HttpVersion httpVersion = HttpVersion.valueOf(new String(resource.readFor(request))); 20 | response.setProtocolVersion(httpVersion); 21 | } 22 | 23 | @Override 24 | public ResponseHandler apply(final MocoConfig config) { 25 | return this; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/bootstrap/ShutdownPortOption.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.bootstrap; 2 | 3 | import com.google.common.base.Optional; 4 | import org.apache.commons.cli.Option; 5 | 6 | public class ShutdownPortOption { 7 | private Optional shutdownPort; 8 | 9 | public ShutdownPortOption(Integer shutdownPort) { 10 | this.shutdownPort = Optional.fromNullable(shutdownPort); 11 | } 12 | 13 | public boolean hasShutdonwPort() { 14 | return shutdownPort.isPresent(); 15 | } 16 | 17 | public int getShutdownPort(int defaultShutDownPort) { 18 | return shutdownPort.or(defaultShutDownPort); 19 | } 20 | 21 | public static Option shutdownPortOption() { 22 | Option opt = new Option("s", true, "shutdown port"); 23 | opt.setType(String.class); 24 | opt.setRequired(false); 25 | return opt; 26 | } 27 | 28 | public static Integer getShutdownPort(String shutdownPort) { 29 | return shutdownPort == null ? null : Integer.valueOf(shutdownPort); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/matcher/AndRequestMatcher.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.matcher; 2 | 3 | import com.github.dreamhead.moco.MocoConfig; 4 | import com.github.dreamhead.moco.RequestMatcher; 5 | import io.netty.handler.codec.http.FullHttpRequest; 6 | 7 | public class AndRequestMatcher extends CompositeRequestMatcher { 8 | public AndRequestMatcher(Iterable matchers) { 9 | super(matchers); 10 | } 11 | 12 | @Override 13 | public boolean match(FullHttpRequest request) { 14 | for (RequestMatcher matcher : matchers) { 15 | if (!matcher.match(request)) { 16 | return false; 17 | } 18 | } 19 | 20 | return true; 21 | } 22 | 23 | @Override 24 | public RequestMatcher apply(final MocoConfig config) { 25 | Iterable appliedMatchers = applyToMatchers(config); 26 | if (appliedMatchers == this.matchers) { 27 | return this; 28 | } 29 | 30 | return new AndRequestMatcher(appliedMatchers); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/matcher/OrRequestMatcher.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.matcher; 2 | 3 | import com.github.dreamhead.moco.MocoConfig; 4 | import com.github.dreamhead.moco.RequestMatcher; 5 | import io.netty.handler.codec.http.FullHttpRequest; 6 | 7 | public class OrRequestMatcher extends CompositeRequestMatcher { 8 | public OrRequestMatcher(Iterable matchers) { 9 | super(matchers); 10 | } 11 | 12 | @Override 13 | public boolean match(FullHttpRequest request) { 14 | for (RequestMatcher matcher : matchers) { 15 | if (matcher.match(request)) { 16 | return true; 17 | } 18 | } 19 | 20 | return false; 21 | } 22 | 23 | @Override 24 | public RequestMatcher apply(final MocoConfig config) { 25 | Iterable appliedMatchers = applyToMatchers(config); 26 | if (appliedMatchers == this.matchers) { 27 | return this; 28 | } 29 | 30 | return new OrRequestMatcher(applyToMatchers(config)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/extractor/JsonPathRequestExtractor.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.extractor; 2 | 3 | import com.github.dreamhead.moco.RequestExtractor; 4 | import com.jayway.jsonpath.JsonPath; 5 | import io.netty.handler.codec.http.FullHttpRequest; 6 | 7 | import java.util.List; 8 | 9 | public class JsonPathRequestExtractor implements RequestExtractor { 10 | private final ContentRequestExtractor extractor = new ContentRequestExtractor(); 11 | private final JsonPath jsonPath; 12 | 13 | public JsonPathRequestExtractor(String jsonPath) { 14 | this.jsonPath = JsonPath.compile(jsonPath); 15 | } 16 | 17 | @Override 18 | public String[] extract(FullHttpRequest request) { 19 | return toStringArray(jsonPath.read(extractor.extract(request))); 20 | } 21 | 22 | @SuppressWarnings("unchecked") 23 | private String[] toStringArray(Object content){ 24 | if(content instanceof List){ 25 | List texts = (List) content; 26 | return texts.toArray(new String[texts.size()]); 27 | } 28 | 29 | return new String[]{content.toString()}; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/extractor/Extractors.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.extractor; 2 | 3 | import com.github.dreamhead.moco.RequestExtractor; 4 | import com.google.common.collect.ImmutableMap; 5 | 6 | import static java.lang.String.format; 7 | 8 | public class Extractors { 9 | private static ImmutableMap> extractors = ImmutableMap.>builder() 10 | .put("file", new ContentRequestExtractor()) 11 | .put("text", new ContentRequestExtractor()) 12 | .put("pathresource", new ContentRequestExtractor()) 13 | .put("uri", new UriRequestExtractor()) 14 | .put("method", new HttpMethodExtractor()) 15 | .put("version", new VersionExtractor()).build(); 16 | 17 | public static RequestExtractor extractor(String id) { 18 | if (extractors.containsKey(id)) { 19 | return extractors.get(id); 20 | } 21 | 22 | throw new IllegalArgumentException(format("unknown extractor for [%s]", id)); 23 | } 24 | 25 | private Extractors() {} 26 | } 27 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/handler/ContentHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.handler; 2 | 3 | import com.github.dreamhead.moco.MocoConfig; 4 | import com.github.dreamhead.moco.ResponseHandler; 5 | import com.github.dreamhead.moco.resource.ContentResource; 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.handler.codec.http.FullHttpRequest; 8 | 9 | public class ContentHandler extends AbstractContentResponseHandler { 10 | private final ContentResource resource; 11 | 12 | public ContentHandler(final ContentResource resource) { 13 | this.resource = resource; 14 | } 15 | 16 | @Override 17 | protected void writeContentResponse(FullHttpRequest request, ByteBuf buffer) { 18 | buffer.writeBytes(this.resource.readFor(request)); 19 | } 20 | 21 | @Override 22 | protected String getContentType(FullHttpRequest request) { 23 | return resource.getContentType(); 24 | } 25 | 26 | @Override 27 | public ResponseHandler apply(final MocoConfig config) { 28 | return new ContentHandler((ContentResource)this.resource.apply(config)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/resource/Resource.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.resource; 2 | 3 | import com.github.dreamhead.moco.ConfigApplier; 4 | import com.github.dreamhead.moco.MocoConfig; 5 | import io.netty.handler.codec.http.FullHttpRequest; 6 | 7 | public class Resource implements Identifiable, ConfigApplier, ResourceReader { 8 | private Identifiable identifiable; 9 | private ResourceConfigApplier configApplier; 10 | protected ResourceReader reader; 11 | 12 | public Resource(Identifiable identifiable, ResourceConfigApplier configApplier, ResourceReader reader) { 13 | this.identifiable = identifiable; 14 | this.configApplier = configApplier; 15 | this.reader = reader; 16 | } 17 | 18 | @Override 19 | public Resource apply(MocoConfig config) { 20 | return configApplier.apply(config, this); 21 | } 22 | 23 | @Override 24 | public String id() { 25 | return identifiable.id(); 26 | } 27 | 28 | @Override 29 | public byte[] readFor(FullHttpRequest request) { 30 | return reader.readFor(request); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2013 ZHENG Ye 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/MocoMount.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import com.github.dreamhead.moco.mount.MountPredicate; 4 | import com.github.dreamhead.moco.mount.MountTo; 5 | 6 | import static org.apache.commons.io.FilenameUtils.wildcardMatch; 7 | 8 | public class MocoMount { 9 | public static MountTo to(String target) { 10 | return new MountTo(target); 11 | } 12 | 13 | public static MountPredicate include(final String wildcard) { 14 | return new MountPredicate() { 15 | @Override 16 | public boolean apply(String filename) { 17 | return wildcardMatch(filename, wildcard); 18 | } 19 | }; 20 | } 21 | 22 | public static MountPredicate exclude(String wildcard) { 23 | return not(include(wildcard)); 24 | } 25 | 26 | private static MountPredicate not(final MountPredicate predicate) { 27 | return new MountPredicate() { 28 | @Override 29 | public boolean apply(String filename) { 30 | return !predicate.apply(filename); 31 | } 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/runner/monitor/FilesMonitor.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.runner.monitor; 2 | 3 | import com.google.common.base.Function; 4 | import org.apache.commons.io.monitor.FileAlterationListener; 5 | 6 | import java.io.File; 7 | 8 | import static com.google.common.collect.FluentIterable.from; 9 | 10 | public class FilesMonitor implements Monitor { 11 | private final Iterable monitors; 12 | 13 | public FilesMonitor(Iterable files, final FileAlterationListener listener) { 14 | this.monitors = from(files).transform(new Function() { 15 | @Override 16 | public FileMonitor apply(File file) { 17 | return new FileMonitor(file, listener); 18 | } 19 | }); 20 | } 21 | 22 | @Override 23 | public void startMonitor() { 24 | for (FileMonitor monitor : monitors) { 25 | monitor.startMonitor(); 26 | } 27 | } 28 | 29 | @Override 30 | public void stopMonitor() { 31 | for (FileMonitor monitor : monitors) { 32 | monitor.stopMonitor(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/internal/MocoClient.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.internal; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.channel.*; 5 | import io.netty.channel.nio.NioEventLoopGroup; 6 | import io.netty.channel.socket.nio.NioSocketChannel; 7 | 8 | public class MocoClient { 9 | public void run(final String host, final int port, final ChannelHandler pipelineFactory) { 10 | EventLoopGroup group = new NioEventLoopGroup(); 11 | Bootstrap bootstrap = new Bootstrap(); 12 | bootstrap.group(group) 13 | .channel(NioSocketChannel.class) 14 | .remoteAddress(host, port) 15 | .option(ChannelOption.TCP_NODELAY, true) 16 | .handler(pipelineFactory); 17 | 18 | try { 19 | Channel channel = bootstrap.connect().sync().channel(); 20 | ChannelFuture future = channel.closeFuture().sync(); 21 | future.addListener(ChannelFutureListener.CLOSE); 22 | } catch (InterruptedException e) { 23 | throw new RuntimeException(e); 24 | } finally { 25 | group.shutdownGracefully(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /moco-core/src/test/java/com/github/dreamhead/moco/extractor/UriRequestExtractorTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.extractor; 2 | 3 | import io.netty.handler.codec.http.FullHttpRequest; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import static org.hamcrest.CoreMatchers.is; 8 | import static org.junit.Assert.assertThat; 9 | import static org.mockito.Mockito.mock; 10 | import static org.mockito.Mockito.when; 11 | 12 | public class UriRequestExtractorTest { 13 | private UriRequestExtractor extractor; 14 | private FullHttpRequest request; 15 | 16 | @Before 17 | public void setUp() throws Exception { 18 | extractor = new UriRequestExtractor(); 19 | request = mock(FullHttpRequest.class); 20 | } 21 | 22 | @Test 23 | public void should_return_all_uri_while_uri_has_no_parameter() { 24 | when(request.getUri()).thenReturn("/foo"); 25 | assertThat(extractor.extract(request), is("/foo")); 26 | } 27 | 28 | @Test 29 | public void should_return_path_part_while_uri_has_parameters() { 30 | when(request.getUri()).thenReturn("/foo?param=bar"); 31 | assertThat(extractor.extract(request), is("/foo")); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /moco-core/src/test/resources/many_content_failover.response: -------------------------------------------------------------------------------- 1 | [ { 2 | "request" : { 3 | "version" : "HTTP/1.1", 4 | "content" : "", 5 | "headers" : { 6 | "Host" : "localhost:12306", 7 | "User-Agent" : "Apache-HttpClient/4.2.5 (java 1.5)", 8 | "Connection" : "Keep-Alive" 9 | }, 10 | "queries" : null, 11 | "method" : "GET" 12 | }, 13 | "response" : { 14 | "version" : "HTTP/1.1", 15 | "content" : "get_proxy", 16 | "headers" : { 17 | "Content-Length" : "9", 18 | "Content-Type" : "text/plain; charset=UTF-8" 19 | }, 20 | "statusCode" : 200 21 | } 22 | }, { 23 | "request" : { 24 | "version" : "HTTP/1.1", 25 | "content" : "proxy", 26 | "headers" : { 27 | "Host" : "localhost:12306", 28 | "Content-Length" : "5", 29 | "User-Agent" : "Apache-HttpClient/4.2.5 (java 1.5)", 30 | "Connection" : "Keep-Alive" 31 | }, 32 | "queries" : null, 33 | "method" : "POST" 34 | }, 35 | "response" : { 36 | "version" : "HTTP/1.1", 37 | "content" : "post_proxy", 38 | "headers" : { 39 | "Content-Length" : "10", 40 | "Content-Type" : "text/plain; charset=UTF-8" 41 | }, 42 | "statusCode" : 200 43 | } 44 | } ] -------------------------------------------------------------------------------- /moco-runner/src/test/java/com/github/dreamhead/moco/MocoPathResourceTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import com.google.common.io.ByteStreams; 4 | import org.junit.Test; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | 9 | import static com.github.dreamhead.moco.RemoteTestUtils.remoteUrl; 10 | import static com.github.dreamhead.moco.RemoteTestUtils.root; 11 | import static org.hamcrest.CoreMatchers.is; 12 | import static org.junit.Assert.assertThat; 13 | 14 | public class MocoPathResourceTest extends AbstractMocoStandaloneTest { 15 | @Test 16 | public void should_return_path_resource_based_on_specified_request() throws IOException { 17 | runWithConfiguration("path_resource.json"); 18 | assertThat(helper.get(remoteUrl("/path-resource")), is("response from path")); 19 | } 20 | 21 | @Test 22 | public void should_return_response_based_on_path_resource() throws IOException { 23 | runWithConfiguration("path_resource.json"); 24 | 25 | InputStream asStream = this.getClass().getClassLoader().getResourceAsStream("path.request"); 26 | assertThat(helper.postBytes(root(), ByteStreams.toByteArray(asStream)), is("path resource")); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/matcher/EqRequestMatcher.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.matcher; 2 | 3 | import com.github.dreamhead.moco.MocoConfig; 4 | import com.github.dreamhead.moco.RequestExtractor; 5 | import com.github.dreamhead.moco.RequestMatcher; 6 | import com.github.dreamhead.moco.resource.Resource; 7 | import com.google.common.base.Predicate; 8 | 9 | import java.util.Arrays; 10 | 11 | public class EqRequestMatcher extends AbstractOperatorMatcher { 12 | private final Resource expected; 13 | 14 | public EqRequestMatcher(final RequestExtractor extractor, final Resource expected) { 15 | super(extractor, new Predicate() { 16 | @Override 17 | public boolean apply(String input) { 18 | return Arrays.equals(input.getBytes(), expected.readFor(null)); 19 | } 20 | }); 21 | this.expected = expected; 22 | } 23 | 24 | @Override 25 | public RequestMatcher apply(final MocoConfig config) { 26 | Resource appliedResource = expected.apply(config); 27 | if (appliedResource == expected) { 28 | return this; 29 | } 30 | 31 | return new EqRequestMatcher(extractor, appliedResource); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/handler/AbstractContentResponseHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.handler; 2 | 3 | import com.github.dreamhead.moco.ResponseHandler; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.Unpooled; 6 | import io.netty.handler.codec.http.FullHttpRequest; 7 | import io.netty.handler.codec.http.FullHttpResponse; 8 | 9 | public abstract class AbstractContentResponseHandler implements ResponseHandler { 10 | private final ContentTypeDetector detector = new ContentTypeDetector(); 11 | 12 | protected abstract void writeContentResponse(FullHttpRequest request, ByteBuf buffer); 13 | 14 | @Override 15 | public void writeToResponse(FullHttpRequest request, FullHttpResponse response) { 16 | ByteBuf buffer = Unpooled.buffer(); 17 | writeContentResponse(request, buffer); 18 | response.content().writeBytes(buffer); 19 | response.headers().set("Content-Length", buffer.writerIndex()); 20 | if (!detector.hasContentType(response)) { 21 | response.headers().set("Content-Type", getContentType(request)); 22 | } 23 | } 24 | 25 | protected String getContentType(FullHttpRequest request) { 26 | return "text/html; charset=UTF-8"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /moco-core/src/test/java/com/github/dreamhead/moco/extractor/VersionExtractorTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.extractor; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.junit.Assert.assertThat; 5 | import io.netty.handler.codec.http.DefaultFullHttpRequest; 6 | import io.netty.handler.codec.http.FullHttpRequest; 7 | import io.netty.handler.codec.http.HttpMethod; 8 | import io.netty.handler.codec.http.HttpVersion; 9 | 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | 13 | public class VersionExtractorTest { 14 | private VersionExtractor extractor; 15 | private FullHttpRequest request; 16 | 17 | @Before 18 | public void setUp() { 19 | extractor = new VersionExtractor(); 20 | } 21 | 22 | @Test 23 | public void shoud_get_right_version_with_HTTP_1_0() { 24 | request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_0, 25 | HttpMethod.GET, "/foo"); 26 | assertThat(extractor.extract(request), 27 | is(HttpVersion.HTTP_1_0.toString())); 28 | } 29 | 30 | @Test 31 | public void shoud_get_right_version_with_HTTP_1_1() { 32 | request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, 33 | HttpMethod.GET, "/foo"); 34 | assertThat(extractor.extract(request), 35 | is(HttpVersion.HTTP_1_1.toString())); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/handler/HeaderResponseHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.handler; 2 | 3 | import com.github.dreamhead.moco.MocoConfig; 4 | import com.github.dreamhead.moco.ResponseHandler; 5 | import com.github.dreamhead.moco.resource.Resource; 6 | import io.netty.handler.codec.http.*; 7 | 8 | public class HeaderResponseHandler implements ResponseHandler { 9 | private final ContentTypeDetector detector = new ContentTypeDetector(); 10 | 11 | private final String name; 12 | private final Resource resource; 13 | 14 | public HeaderResponseHandler(String name, Resource resource) { 15 | this.name = name; 16 | this.resource = resource; 17 | } 18 | 19 | @Override 20 | public void writeToResponse(FullHttpRequest request, FullHttpResponse response) { 21 | if (detector.hasHeader(response, name)) { 22 | response.headers().remove(name); 23 | } 24 | 25 | HttpHeaders.addHeader(response, name, new String(resource.readFor(request))); 26 | } 27 | 28 | @Override 29 | public ResponseHandler apply(final MocoConfig config) { 30 | if (config.isFor(resource.id())) { 31 | return new HeaderResponseHandler(name, resource.apply(config)); 32 | } 33 | 34 | return this; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/internal/MocoEventListener.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.internal; 2 | 3 | import com.github.dreamhead.moco.dumper.Dumper; 4 | import com.github.dreamhead.moco.dumper.HttpRequestDumper; 5 | import com.github.dreamhead.moco.dumper.HttpResponseDumper; 6 | import com.google.common.eventbus.Subscribe; 7 | import io.netty.handler.codec.http.FullHttpRequest; 8 | import io.netty.handler.codec.http.FullHttpResponse; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | public class MocoEventListener { 13 | private static Logger logger = LoggerFactory.getLogger(MocoEventListener.class); 14 | private final Dumper requestDumper = new HttpRequestDumper(); 15 | private final Dumper responseDumper = new HttpResponseDumper(); 16 | 17 | @Subscribe 18 | public void onMessageArrived(FullHttpRequest request) { 19 | logger.info("Request received:\n\n{}\n", requestDumper.dump(request)); 20 | } 21 | 22 | @Subscribe 23 | public void onException(Exception e) { 24 | logger.error("Exception thrown", e); 25 | } 26 | 27 | @Subscribe 28 | public void onMessageLeave(FullHttpResponse response) { 29 | logger.info("Response return:\n\n{}\n", responseDumper.dump(response)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/parser/model/TextContainerDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.parser.model; 2 | 3 | import com.fasterxml.jackson.core.JsonParser; 4 | import com.fasterxml.jackson.core.JsonToken; 5 | import com.fasterxml.jackson.databind.DeserializationContext; 6 | import com.fasterxml.jackson.databind.JsonDeserializer; 7 | 8 | import java.io.IOException; 9 | 10 | public class TextContainerDeserializer extends JsonDeserializer { 11 | @Override 12 | public TextContainer deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { 13 | JsonToken currentToken = jp.getCurrentToken(); 14 | if (currentToken == JsonToken.VALUE_STRING) { 15 | return new TextContainer(jp.getText().trim(), null); 16 | } else if (currentToken == JsonToken.START_OBJECT) { 17 | JsonToken jsonToken = jp.nextToken(); 18 | if (jsonToken == JsonToken.FIELD_NAME) { 19 | String operation = jp.getText().trim(); 20 | jp.nextToken(); 21 | String text = jp.getText().trim(); 22 | jp.nextToken(); 23 | return new TextContainer(text, operation); 24 | } 25 | } 26 | 27 | throw ctxt.mappingException(TextContainer.class, currentToken); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /moco-core/src/test/java/com/github/dreamhead/moco/helper/MocoTestHelper.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.helper; 2 | 3 | import com.google.common.io.Resources; 4 | import org.apache.http.client.fluent.Content; 5 | import org.apache.http.client.fluent.Request; 6 | 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | 10 | import static com.google.common.io.ByteStreams.toByteArray; 11 | 12 | public class MocoTestHelper { 13 | public String get(String uri) throws IOException { 14 | Content content = Request.Get(uri).execute().returnContent(); 15 | return content.asString(); 16 | } 17 | 18 | public String postContent(String uri, String postContent) throws IOException { 19 | return postBytes(uri, postContent.getBytes()); 20 | } 21 | 22 | public String postBytes(String uri, byte[] bytes) throws IOException { 23 | Content content = Request.Post(uri).bodyByteArray(bytes) 24 | .execute().returnContent(); 25 | return content.asString(); 26 | } 27 | 28 | public String postFile(String uri, String file) throws IOException { 29 | InputStream is = Resources.getResource(file).openStream(); 30 | Content content = Request.Post(uri).bodyByteArray(toByteArray(is)) 31 | .execute().returnContent(); 32 | return content.asString(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /moco-doc/ReleaseNote.md: -------------------------------------------------------------------------------- 1 | # Release 0.8.1 (1-August-2013) 2 | * Upgrade to Netty 4 3 | * Fixed proxy bug 4 | 5 | # Release 0.8 (21-July-2013) 6 | 7 | ## Usage 8 | * global setting file 9 | 10 | ## APIs 11 | ### Request 12 | * JSONPath 13 | ### Response 14 | #### Integration 15 | * proxy 16 | * with 17 | * (Beta) template 18 | * removed url (using proxy instead) 19 | * removed content (using with instead) 20 | 21 | #### Misc Response 22 | * removed cache 23 | 24 | ## Settings 25 | * context 26 | * file root 27 | * environment 28 | 29 | # Release 0.7 (1-May-2013) 30 | 31 | the first public release of Moco 32 | 33 | ## Usage 34 | * API 35 | * stanalone server 36 | * shell 37 | * maven plugin (external) 38 | * gradle plugin (external) 39 | 40 | ## APIs 41 | 42 | ### Request 43 | 44 | #### HTTP 45 | * content(text, file) 46 | * URI 47 | * query Parameter 48 | * HTTP method 49 | * version 50 | * header 51 | * redirect 52 | 53 | #### Operator 54 | * eq 55 | * match 56 | 57 | #### Web 58 | * cookie 59 | * form 60 | 61 | #### Integration 62 | * XML 63 | * XPath 64 | * JSON 65 | 66 | ### Response 67 | 68 | #### HTTP 69 | * content(text, file) 70 | * version 71 | * status code 72 | * header 73 | 74 | #### Web 75 | * cookie 76 | 77 | #### Integration 78 | * url 79 | 80 | #### Misc Response 81 | * latency 82 | * sequence(API only) 83 | * cache 84 | 85 | ### Misc 86 | * mount -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/matcher/CompositeRequestMatcher.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.matcher; 2 | 3 | import com.github.dreamhead.moco.MocoConfig; 4 | import com.github.dreamhead.moco.RequestMatcher; 5 | import com.google.common.base.Function; 6 | import com.google.common.collect.FluentIterable; 7 | 8 | import static com.google.common.collect.FluentIterable.from; 9 | 10 | public abstract class CompositeRequestMatcher implements RequestMatcher { 11 | protected final Iterable matchers; 12 | 13 | public CompositeRequestMatcher(Iterable matchers) { 14 | this.matchers = matchers; 15 | } 16 | 17 | protected Iterable applyToMatchers(MocoConfig config) { 18 | FluentIterable appliedMatchers = from(matchers).transform(applyConfig(config)); 19 | if (matchers.equals(appliedMatchers)) { 20 | return this.matchers; 21 | } 22 | 23 | return appliedMatchers; 24 | } 25 | 26 | private Function applyConfig(final MocoConfig config) { 27 | return new Function() { 28 | @Override 29 | public RequestMatcher apply(RequestMatcher matcher) { 30 | return matcher.apply(config); 31 | } 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /moco-runner/src/test/java/com/github/dreamhead/moco/AbstractMocoStandaloneTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import com.github.dreamhead.moco.helper.MocoTestHelper; 4 | import com.github.dreamhead.moco.runner.JsonRunner; 5 | import com.google.common.io.Resources; 6 | import org.junit.After; 7 | 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.util.List; 11 | 12 | import static com.github.dreamhead.moco.RemoteTestUtils.port; 13 | import static com.github.dreamhead.moco.runner.JsonRunner.newJsonRunnerWithStreams; 14 | import static com.google.common.collect.Lists.newArrayList; 15 | 16 | public class AbstractMocoStandaloneTest { 17 | protected final MocoTestHelper helper = new MocoTestHelper(); 18 | private JsonRunner runner; 19 | 20 | @After 21 | public void teardown() { 22 | runner.stop(); 23 | } 24 | 25 | protected void runWithConfiguration(String... resourceNames) { 26 | try { 27 | List streams = newArrayList(); 28 | for (String resourceName : resourceNames) { 29 | streams.add(Resources.getResource(resourceName).openStream()); 30 | } 31 | runner = newJsonRunnerWithStreams(streams, port()); 32 | runner.run(); 33 | } catch (IOException e) { 34 | throw new RuntimeException(e); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/util/Cookies.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.util; 2 | 3 | import com.google.common.base.Predicate; 4 | import io.netty.handler.codec.http.Cookie; 5 | import io.netty.handler.codec.http.CookieDecoder; 6 | import io.netty.handler.codec.http.DefaultCookie; 7 | import io.netty.handler.codec.http.ServerCookieEncoder; 8 | 9 | import java.util.Set; 10 | 11 | import static com.google.common.collect.Iterables.find; 12 | 13 | public class Cookies { 14 | public String encodeCookie(String key, String value) { 15 | Cookie cookie = new DefaultCookie(key, value); 16 | cookie.setPath("/"); 17 | return ServerCookieEncoder.encode(cookie); 18 | } 19 | 20 | public String decodeCookie(String cookieString, String key) { 21 | if(cookieString == null) { 22 | return null; 23 | } 24 | 25 | Set cookies = CookieDecoder.decode(cookieString); 26 | 27 | Cookie cookie = find(cookies, byCookieName(key), null); 28 | return cookie == null ? null : cookie.getValue(); 29 | } 30 | 31 | private static Predicate byCookieName(final String key) { 32 | return new Predicate() { 33 | @Override 34 | public boolean apply(Cookie cookie) { 35 | return key.equals(cookie.getName()); 36 | } 37 | }; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/parser/GlobalSettingParser.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.parser; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; 5 | import com.fasterxml.jackson.databind.type.TypeFactory; 6 | import com.github.dreamhead.moco.parser.model.GlobalSetting; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.util.List; 13 | 14 | import static java.lang.String.format; 15 | 16 | public class GlobalSettingParser { 17 | private static Logger logger = LoggerFactory.getLogger(GlobalSettingParser.class); 18 | private final ObjectMapper mapper = new ObjectMapper(); 19 | private final TypeFactory factory = TypeFactory.defaultInstance(); 20 | 21 | public List parse(InputStream is) { 22 | try { 23 | return mapper.readValue(is, factory.constructCollectionType(List.class, GlobalSetting.class)); 24 | } catch (UnrecognizedPropertyException upe) { 25 | logger.info("Unrecognized field: {}", upe.getMessage()); 26 | throw new RuntimeException(format("Unrecognized field [ %s ], please check!", upe.getUnrecognizedPropertyName())); 27 | } catch (IOException e) { 28 | throw new RuntimeException(e); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /moco-runner/src/test/java/com/github/dreamhead/moco/runner/DynamicConfigurationRunnerTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.runner; 2 | 3 | import com.github.dreamhead.moco.bootstrap.StartArgs; 4 | import org.junit.Test; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | 9 | import static com.github.dreamhead.moco.RemoteTestUtils.port; 10 | import static com.github.dreamhead.moco.RemoteTestUtils.root; 11 | import static org.hamcrest.CoreMatchers.is; 12 | import static org.junit.Assert.assertThat; 13 | 14 | public class DynamicConfigurationRunnerTest extends AbstractRunnerTest { 15 | @Test 16 | public void should_reload_configuration() throws IOException, InterruptedException { 17 | final File config = File.createTempFile("config", ".json"); 18 | changeFileContent(config, "[{\"response\" :{" + 19 | "\"text\" : \"foo\"" + 20 | "}}]"); 21 | 22 | RunnerFactory factory = new RunnerFactory(9090, "SHUTDOWN"); 23 | runner = factory.createRunner(new StartArgs(port(), 9090, config.getAbsolutePath(), null, null)); 24 | runner.run(); 25 | assertThat(helper.get(root()), is("foo")); 26 | 27 | changeFileContent(config, "[{\"response\" :{" + 28 | "\"text\" : \"foobar\"" + 29 | "}}]"); 30 | 31 | waitChangeHappens(); 32 | 33 | assertThat(helper.get(root()), is("foobar")); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/internal/MocoHttpServer.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.internal; 2 | 3 | import io.netty.channel.ChannelInitializer; 4 | import io.netty.channel.ChannelPipeline; 5 | import io.netty.channel.socket.SocketChannel; 6 | import io.netty.handler.codec.http.HttpObjectAggregator; 7 | import io.netty.handler.codec.http.HttpRequestDecoder; 8 | import io.netty.handler.codec.http.HttpResponseEncoder; 9 | 10 | public class MocoHttpServer { 11 | private final MocoServer server = new MocoServer(); 12 | private final ActualHttpServer serverSetting; 13 | 14 | public MocoHttpServer(ActualHttpServer serverSetting) { 15 | this.serverSetting = serverSetting; 16 | } 17 | 18 | public void start() { 19 | server.start(serverSetting.getPort(), new ChannelInitializer() { 20 | @Override 21 | protected void initChannel(SocketChannel ch) throws Exception { 22 | ChannelPipeline pipeline = ch.pipeline(); 23 | pipeline.addLast("decoder", new HttpRequestDecoder()); 24 | pipeline.addLast("aggregator", new HttpObjectAggregator(1048576)); 25 | pipeline.addLast("encoder", new HttpResponseEncoder()); 26 | pipeline.addLast("handler", new MocoHandler(serverSetting)); 27 | } 28 | }); 29 | } 30 | 31 | public void stop() { 32 | server.stop(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/matcher/AbstractOperatorMatcher.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.matcher; 2 | 3 | import com.github.dreamhead.moco.RequestExtractor; 4 | import com.github.dreamhead.moco.RequestMatcher; 5 | import com.google.common.base.Predicate; 6 | import io.netty.handler.codec.http.FullHttpRequest; 7 | 8 | public abstract class AbstractOperatorMatcher implements RequestMatcher { 9 | protected final RequestExtractor extractor; 10 | private final Predicate predicate; 11 | 12 | protected AbstractOperatorMatcher(RequestExtractor extractor, Predicate predicate) { 13 | this.extractor = extractor; 14 | this.predicate = predicate; 15 | } 16 | 17 | @Override 18 | public boolean match(FullHttpRequest request) { 19 | T extractContent = extractor.extract(request); 20 | if (extractContent == null) { 21 | return false; 22 | } 23 | 24 | if (extractContent instanceof String) { 25 | return predicate.apply((String)extractContent); 26 | } 27 | 28 | if (extractContent instanceof String[]) { 29 | String[] contents = (String[])extractContent; 30 | for (String content : contents) { 31 | if (predicate.apply(content)) { 32 | return true; 33 | } 34 | } 35 | 36 | } 37 | 38 | return false; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /moco-runner/src/test/java/com/github/dreamhead/moco/runner/AbstractRunnerTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.runner; 2 | 3 | import com.github.dreamhead.moco.helper.MocoTestHelper; 4 | import com.github.dreamhead.moco.runner.monitor.FileMonitor; 5 | import org.junit.After; 6 | 7 | import java.io.*; 8 | 9 | import static java.lang.String.format; 10 | import static org.junit.Assert.fail; 11 | 12 | public abstract class AbstractRunnerTest { 13 | protected final MocoTestHelper helper = new MocoTestHelper(); 14 | protected Runner runner; 15 | 16 | @After 17 | public void tearDown() { 18 | if (runner != null) { 19 | runner.stop(); 20 | } 21 | } 22 | 23 | protected void waitChangeHappens() { 24 | try { 25 | Thread.sleep(FileMonitor.INTERVAL * 2); 26 | } catch (InterruptedException e) { 27 | fail(format("failed to wait change happens: %s", e.getMessage())); 28 | } 29 | } 30 | 31 | protected void changeFileContent(File response, String content) { 32 | PrintStream stream = null; 33 | try { 34 | stream = new PrintStream(new FileOutputStream(response)); 35 | stream.print(content); 36 | } catch (IOException e) { 37 | fail("failed to change file content"); 38 | } finally { 39 | if (stream != null) { 40 | stream.close(); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/match.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "uri": { 5 | "match": "/\\w*/foo" 6 | } 7 | }, 8 | "response": { 9 | "text": "uri_match" 10 | } 11 | }, 12 | { 13 | "request": { 14 | "uri": "/text-match", 15 | "text": { 16 | "match": "\\w*foo" 17 | } 18 | }, 19 | "response": { 20 | "text": "text_match" 21 | } 22 | }, 23 | { 24 | "request": { 25 | "uri": "/method-match", 26 | "method": { 27 | "match": "get|post" 28 | } 29 | }, 30 | "response": { 31 | "text": "method_match" 32 | } 33 | }, 34 | { 35 | "request": { 36 | "uri": "/header-match", 37 | "headers": { 38 | "content-type": { 39 | "match" : "application/\\w*" 40 | } 41 | } 42 | }, 43 | "response": { 44 | "text": "header_match" 45 | } 46 | }, 47 | { 48 | "request": { 49 | "uri": "/query-match", 50 | "queries": { 51 | "foo": { 52 | "match" : "b\\w*" 53 | } 54 | } 55 | }, 56 | "response": { 57 | "text": "query_match" 58 | } 59 | } 60 | ] -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/ResponseSetting.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import com.github.dreamhead.moco.handler.AndResponseHandler; 4 | import com.github.dreamhead.moco.resource.Resource; 5 | import io.netty.handler.codec.http.HttpResponseStatus; 6 | 7 | import static com.github.dreamhead.moco.Moco.*; 8 | import static com.google.common.collect.Lists.newArrayList; 9 | 10 | public abstract class ResponseSetting { 11 | protected ResponseHandler handler; 12 | 13 | public void response(String content) { 14 | this.response(text(content)); 15 | } 16 | 17 | public void response(Resource resource) { 18 | this.response(with(resource)); 19 | } 20 | 21 | public void response(ResponseHandler handler) { 22 | if (handler == null) { 23 | return; 24 | } 25 | 26 | if (this.handler != null) { 27 | throw new RuntimeException("handler has already been set"); 28 | } 29 | 30 | this.handler = handler; 31 | } 32 | 33 | public void response(ResponseHandler... handler) { 34 | this.response(new AndResponseHandler(newArrayList(handler))); 35 | } 36 | 37 | public void redirectTo(String url) { 38 | this.response(status(HttpResponseStatus.FOUND.code()), header("Location", url)); 39 | } 40 | 41 | protected static RequestMatcher context(String context) { 42 | return match(uri(context + ".*")); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /moco-core/src/test/java/com/github/dreamhead/moco/extractor/HttpMethodExtractorTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.extractor; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.junit.Assert.assertThat; 5 | import io.netty.handler.codec.http.DefaultFullHttpRequest; 6 | import io.netty.handler.codec.http.FullHttpRequest; 7 | import io.netty.handler.codec.http.HttpMethod; 8 | import io.netty.handler.codec.http.HttpVersion; 9 | 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | 13 | public class HttpMethodExtractorTest { 14 | private HttpMethodExtractor extractor; 15 | private FullHttpRequest request; 16 | 17 | @Before 18 | public void setUp() { 19 | extractor = new HttpMethodExtractor(); 20 | } 21 | 22 | @Test 23 | public void should_return_get_with_get_method() { 24 | request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.GET, "/foo"); 25 | assertThat(extractor.extract(request), is(HttpMethod.GET.toString())); 26 | } 27 | 28 | @Test 29 | public void should_return_post_with_post_method() { 30 | request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.POST, "/foo"); 31 | assertThat(extractor.extract(request), is(HttpMethod.POST.toString())); 32 | } 33 | 34 | @Test 35 | public void should_return_delete_with_delete_method() { 36 | request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.DELETE, "/foo"); 37 | assertThat(extractor.extract(request), is(HttpMethod.DELETE.toString())); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/extractor/FormRequestExtractor.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.extractor; 2 | 3 | import com.github.dreamhead.moco.RequestExtractor; 4 | import io.netty.handler.codec.http.FullHttpRequest; 5 | import io.netty.handler.codec.http.multipart.Attribute; 6 | import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory; 7 | import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder; 8 | import io.netty.handler.codec.http.multipart.InterfaceHttpData; 9 | 10 | public class FormRequestExtractor implements RequestExtractor { 11 | private final String key; 12 | 13 | public FormRequestExtractor(String key) { 14 | this.key = key; 15 | } 16 | 17 | @Override 18 | public String extract(FullHttpRequest request) { 19 | try { 20 | HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(false), request); 21 | InterfaceHttpData data = decoder.getBodyHttpData(key); 22 | if (data != null && data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) { 23 | Attribute attribute = (Attribute)data; 24 | return attribute.getValue(); 25 | } 26 | 27 | return null; 28 | } catch (HttpPostRequestDecoder.IncompatibleDataDecoderException idde) { 29 | return null; 30 | } catch (Exception e) { 31 | throw new RuntimeException(e); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/HttpServer.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import com.github.dreamhead.moco.mount.MountHandler; 4 | import com.github.dreamhead.moco.mount.MountMatcher; 5 | import com.github.dreamhead.moco.mount.MountPredicate; 6 | import com.github.dreamhead.moco.mount.MountTo; 7 | import io.netty.handler.codec.http.HttpMethod; 8 | 9 | import java.io.File; 10 | 11 | import static com.github.dreamhead.moco.Moco.*; 12 | import static com.google.common.collect.ImmutableList.copyOf; 13 | 14 | public abstract class HttpServer extends ResponseSetting { 15 | protected abstract Setting onRequestAttached(RequestMatcher matcher); 16 | 17 | public Setting request(RequestMatcher matcher) { 18 | return this.onRequestAttached(matcher); 19 | } 20 | 21 | public Setting request(RequestMatcher... matchers) { 22 | return request(or(matchers)); 23 | } 24 | 25 | public Setting get(RequestMatcher matcher) { 26 | return request(and(by(method(HttpMethod.GET.name())), matcher)); 27 | } 28 | 29 | public Setting post(RequestMatcher matcher) { 30 | return request(and(by(method(HttpMethod.POST.name())), matcher)); 31 | } 32 | 33 | public void mount(final String dir, final MountTo target, final MountPredicate... predicates) { 34 | File mountedDir = new File(dir); 35 | this.request(new MountMatcher(mountedDir, target, copyOf(predicates))).response(new MountHandler(mountedDir, target)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/handler/AndResponseHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.handler; 2 | 3 | import com.github.dreamhead.moco.MocoConfig; 4 | import com.github.dreamhead.moco.ResponseHandler; 5 | import com.google.common.base.Function; 6 | import io.netty.handler.codec.http.FullHttpRequest; 7 | import io.netty.handler.codec.http.FullHttpResponse; 8 | 9 | import static com.google.common.collect.FluentIterable.from; 10 | 11 | public class AndResponseHandler implements ResponseHandler { 12 | private final Iterable handlers; 13 | 14 | public AndResponseHandler(Iterable handlers) { 15 | this.handlers = handlers; 16 | } 17 | 18 | @Override 19 | public void writeToResponse(FullHttpRequest request, FullHttpResponse response) { 20 | for (ResponseHandler handler : handlers) { 21 | handler.writeToResponse(request, response); 22 | } 23 | } 24 | 25 | @Override 26 | public ResponseHandler apply(final MocoConfig config) { 27 | return new AndResponseHandler(from(handlers).transform(applyConfig(config))); 28 | } 29 | 30 | private Function applyConfig(final MocoConfig config) { 31 | return new Function() { 32 | @Override 33 | public ResponseHandler apply(ResponseHandler handler) { 34 | return handler.apply(config); 35 | } 36 | }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/bootstrap/StartTask.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.bootstrap; 2 | 3 | import com.github.dreamhead.moco.runner.*; 4 | 5 | import static com.github.dreamhead.moco.bootstrap.StartArgs.parse; 6 | 7 | public class StartTask implements BootstrapTask { 8 | private final int defaultShutdownPort; 9 | private final RunnerFactory factory; 10 | 11 | public StartTask(int defaultShutdownPort, String defaultShutdownKey) { 12 | this.factory = new RunnerFactory(defaultShutdownPort, defaultShutdownKey); 13 | this.defaultShutdownPort = defaultShutdownPort; 14 | } 15 | 16 | @Override 17 | public void run(String[] args) { 18 | StartArgs startArgs = parse(args); 19 | if (conflictWithDefaultShutdownPort(startArgs, defaultShutdownPort)) { 20 | System.err.println("port is same as default shutdown port, please specify another port or shutdown port."); 21 | return; 22 | } 23 | 24 | final Runner runner = factory.createRunner(startArgs); 25 | runner.run(); 26 | 27 | Runtime.getRuntime().addShutdownHook(new Thread() { 28 | @Override 29 | public void run() { 30 | runner.stop(); 31 | } 32 | }); 33 | } 34 | 35 | private boolean conflictWithDefaultShutdownPort(StartArgs startArgs, int defaultShutdownPort) { 36 | return startArgs.getPort() == defaultShutdownPort && !startArgs.hasShutdonwPort(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/util/FileContentType.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.util; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import com.google.common.io.Files; 5 | 6 | public class FileContentType { 7 | public static final String DEFAULT_CONTENT_TYPE = "text/plain; charset=UTF-8"; 8 | 9 | private static final ImmutableMap contentTypeMap = ImmutableMap.builder() 10 | .put("png", "image/png") 11 | .put("gif", "image/gif") 12 | .put("jpg", "image/jpeg") 13 | .put("jpeg", "image/jpeg") 14 | .put("tiff", "image/tiff") 15 | .put("css", "text/css") 16 | .put("html", "text/html") 17 | .put("txt", "text/plain") 18 | .put("js", "application/x-javascript") 19 | .put("json", "application/json") 20 | .put("pdf", "application/pdf") 21 | .put("zip", "application/zip") 22 | .put("xml", "text/xml") 23 | .build(); 24 | 25 | private final String filename; 26 | 27 | public FileContentType(String filename) { 28 | this.filename = filename; 29 | } 30 | 31 | public String getContentType() { 32 | return toContentType(Files.getFileExtension(filename)); 33 | } 34 | 35 | private String toContentType(String extension) { 36 | String contentType = contentTypeMap.get(extension.toLowerCase()); 37 | return contentType != null ? contentType : DEFAULT_CONTENT_TYPE; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /moco-runner/src/test/java/com/github/dreamhead/moco/MocoXmlStandaloneTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.IOException; 6 | 7 | import static com.github.dreamhead.moco.RemoteTestUtils.remoteUrl; 8 | import static org.hamcrest.CoreMatchers.is; 9 | import static org.junit.Assert.assertThat; 10 | 11 | public class MocoXmlStandaloneTest extends AbstractMocoStandaloneTest { 12 | @Test 13 | public void should_return_expected_response_based_on_specified_xpath_request() throws IOException { 14 | runWithConfiguration("xpath.json"); 15 | assertThat(helper.postFile(remoteUrl("/xpath"), "foo.xml"), is("response_for_xpath_request")); 16 | } 17 | 18 | @Test(expected = IOException.class) 19 | public void should_throw_exception_for_unknown_xpath_request() throws IOException { 20 | runWithConfiguration("xpath.json"); 21 | helper.postFile(remoteUrl("/xpath"), "bar.xml"); 22 | } 23 | 24 | @Test 25 | public void should_return_expected_response_based_on_specified_xml_request() throws IOException { 26 | runWithConfiguration("xml.json"); 27 | assertThat(helper.postFile(remoteUrl("/xml"), "foo.xml"), is("response_for_xml_request")); 28 | } 29 | 30 | @Test 31 | public void should_return_expected_response_based_on_specified_xml_file_request() throws IOException { 32 | runWithConfiguration("xml.json"); 33 | assertThat(helper.postFile(remoteUrl("/xmlfile"), "foo.xml"), is("response_for_xml_file_request")); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/handler/ResponseHandlers.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.handler; 2 | 3 | import com.github.dreamhead.moco.ResponseHandler; 4 | import com.github.dreamhead.moco.resource.Resource; 5 | import com.google.common.collect.ImmutableMap; 6 | 7 | import java.lang.reflect.Constructor; 8 | 9 | import static java.lang.String.format; 10 | 11 | public class ResponseHandlers { 12 | private static final ImmutableMap handlers = ImmutableMap.builder() 13 | .put("file", ContentHandler.class) 14 | .put("text", ContentHandler.class) 15 | .put("pathresource", ContentHandler.class) 16 | .put("template", ContentHandler.class) 17 | .put("version", VersionResponseHandler.class).build(); 18 | 19 | public static ResponseHandler responseHandler(Resource resource) { 20 | if (handlers.containsKey(resource.id())) { 21 | return createResponseHandler(resource); 22 | } 23 | 24 | throw new IllegalArgumentException(format("unknown response handler for [%s]", resource.id())); 25 | } 26 | 27 | private static ResponseHandler createResponseHandler(Resource resource) { 28 | Class clazz = handlers.get(resource.id()); 29 | try { 30 | Constructor[] constructors = clazz.getConstructors(); 31 | return (ResponseHandler)constructors[0].newInstance(resource); 32 | } catch (Exception e) { 33 | throw new RuntimeException(e); 34 | } 35 | } 36 | 37 | private ResponseHandlers() { 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/setting/BaseSetting.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.setting; 2 | 3 | import com.github.dreamhead.moco.ConfigApplier; 4 | import com.github.dreamhead.moco.MocoConfig; 5 | import com.github.dreamhead.moco.RequestMatcher; 6 | import com.github.dreamhead.moco.Setting; 7 | import com.github.dreamhead.moco.matcher.AndRequestMatcher; 8 | import io.netty.handler.codec.http.FullHttpRequest; 9 | import io.netty.handler.codec.http.FullHttpResponse; 10 | 11 | import static com.github.dreamhead.moco.util.Configs.configItem; 12 | import static com.google.common.collect.ImmutableList.of; 13 | 14 | public class BaseSetting extends Setting implements ConfigApplier { 15 | public BaseSetting(RequestMatcher matcher) { 16 | super(matcher); 17 | } 18 | 19 | public boolean match(FullHttpRequest request) { 20 | return this.matcher.match(request) && this.handler != null; 21 | } 22 | 23 | public void writeToResponse(FullHttpRequest request, FullHttpResponse response) { 24 | this.handler.writeToResponse(request, response); 25 | } 26 | 27 | public BaseSetting apply(final MocoConfig config) { 28 | RequestMatcher appliedMatcher = configItem(this.matcher, config); 29 | if (config.isFor("uri") && this.matcher == appliedMatcher) { 30 | appliedMatcher = new AndRequestMatcher(of(appliedMatcher, context(config.apply("")))); 31 | } 32 | 33 | BaseSetting setting = new BaseSetting(appliedMatcher); 34 | setting.handler = configItem(this.handler, config); 35 | return setting; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /moco-runner/src/test/java/com/github/dreamhead/moco/runner/DynamicSettingRunnerTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.runner; 2 | 3 | import com.github.dreamhead.moco.bootstrap.StartArgs; 4 | import org.junit.Test; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | 9 | import static com.github.dreamhead.moco.RemoteTestUtils.port; 10 | import static com.github.dreamhead.moco.RemoteTestUtils.root; 11 | import static org.hamcrest.CoreMatchers.is; 12 | import static org.junit.Assert.assertThat; 13 | 14 | public class DynamicSettingRunnerTest extends AbstractRunnerTest { 15 | @Test 16 | public void should_reload_configuration() throws IOException, InterruptedException { 17 | final File config = File.createTempFile("config", ".json"); 18 | changeFileContent(config, "[{\"response\" :{" + 19 | "\"text\" : \"foo\"" + 20 | "}}]"); 21 | 22 | final File setting = File.createTempFile("setting", ".json"); 23 | changeFileContent(setting, "[{" + 24 | "\"include\" : \"" + config.getAbsolutePath() + "\"" + 25 | "}]"); 26 | 27 | RunnerFactory factory = new RunnerFactory(9090, "SHUTDOWN"); 28 | runner = factory.createRunner(new StartArgs(port(), 9090, null, setting.getAbsolutePath(), null)); 29 | runner.run(); 30 | assertThat(helper.get(root()), is("foo")); 31 | 32 | changeFileContent(config, "[{\"response\" :{" + 33 | "\"text\" : \"foobar\"" + 34 | "}}]"); 35 | 36 | waitChangeHappens(); 37 | 38 | assertThat(helper.get(root()), is("foobar")); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/model/DefaultRequest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 4 | import com.google.common.base.Objects; 5 | 6 | import java.util.Map; 7 | 8 | import static com.google.common.collect.Maps.newHashMap; 9 | 10 | @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) 11 | public class DefaultRequest extends Message implements Request { 12 | private Map queries = newHashMap(); 13 | private String method; 14 | 15 | public void addQuery(String key, String value) { 16 | this.queries.put(key, value); 17 | } 18 | 19 | public Map getQueries() { 20 | return queries; 21 | } 22 | 23 | public void setMethod(String method) { 24 | this.method = method; 25 | } 26 | 27 | public String getMethod() { 28 | return method; 29 | } 30 | 31 | public boolean match(DefaultRequest that) { 32 | return super.match(that) && doMatch(method, that.method) 33 | && doMatch(queries, that.queries); 34 | } 35 | 36 | @Override 37 | public int hashCode() { 38 | return Objects.hashCode(super.hashCode(), queries, method); 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return Objects.toStringHelper(DefaultRequest.class) 44 | .omitNullValues() 45 | .add("version", version) 46 | .add("queries", queries) 47 | .add("method", method) 48 | .add("headers", headers) 49 | .add("content", content) 50 | .toString(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/bootstrap/Bootstrap.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.bootstrap; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | public class Bootstrap implements BootstrapTask { 7 | private static Logger logger = LoggerFactory.getLogger(Bootstrap.class); 8 | private static final int DEFAULT_SHUTDOWN_PORT = 9527; 9 | private static final String DEFAULT_SHUTDOWN_KEY = "_SHUTDOWN_MOCO_KEY"; 10 | 11 | private final BootstrapTask startTask = new StartTask(DEFAULT_SHUTDOWN_PORT, DEFAULT_SHUTDOWN_KEY); 12 | private final BootstrapTask shutdownTask = new ShutdownTask(DEFAULT_SHUTDOWN_PORT, DEFAULT_SHUTDOWN_KEY); 13 | 14 | @Override 15 | public void run(String[] args) { 16 | try { 17 | if (args.length < 1) { 18 | throw new ParseArgException("task name needs to be specified"); 19 | } 20 | 21 | if ("start".equals(args[0])) { 22 | startTask.run(args); 23 | return; 24 | } 25 | 26 | if ("shutdown".equals(args[0])) { 27 | shutdownTask.run(args); 28 | return; 29 | } 30 | 31 | help(); 32 | } catch (ParseArgException e) { 33 | help(); 34 | } catch (Exception e) { 35 | logger.error(e.getMessage()); 36 | } 37 | } 38 | 39 | private void help() { 40 | System.out.println("moco start -p port -c [configuration file] {-s [shutdown port]}"); 41 | System.out.println("or"); 42 | System.out.println("moco start -p port -g [global settings file] {-e [environment]} {-s [shutdown port]}"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /moco-runner/src/test/java/com/github/dreamhead/moco/MocoMountStandaloneTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import org.apache.http.client.HttpResponseException; 4 | import org.junit.Test; 5 | 6 | import java.io.IOException; 7 | 8 | import static com.github.dreamhead.moco.RemoteTestUtils.remoteUrl; 9 | import static org.hamcrest.CoreMatchers.is; 10 | import static org.junit.Assert.assertThat; 11 | 12 | public class MocoMountStandaloneTest extends AbstractMocoStandaloneTest { 13 | @Test 14 | public void should_mount_dir_to_uri() throws IOException { 15 | runWithConfiguration("mount.json"); 16 | assertThat(helper.get(remoteUrl("/mount/mount.response")), is("response from mount")); 17 | } 18 | 19 | @Test 20 | public void should_mount_dir_to_uri_with_include() throws IOException { 21 | runWithConfiguration("mount.json"); 22 | assertThat(helper.get(remoteUrl("/mount-include/mount.response")), is("response from mount")); 23 | } 24 | 25 | @Test(expected = HttpResponseException.class) 26 | public void should_return_non_inclusion() throws IOException { 27 | runWithConfiguration("mount.json"); 28 | helper.get(remoteUrl("/mount-include/foo.bar")); 29 | } 30 | 31 | @Test 32 | public void should_mount_dir_to_uri_with_exclude() throws IOException { 33 | runWithConfiguration("mount.json"); 34 | assertThat(helper.get(remoteUrl("/mount-exclude/foo.bar")), is("foo.bar")); 35 | } 36 | 37 | @Test(expected = HttpResponseException.class) 38 | public void should_return_exclusion() throws IOException { 39 | runWithConfiguration("mount.json"); 40 | helper.get(remoteUrl("/mount-exclude/mount.response")); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /moco-core/src/test/java/com/github/dreamhead/moco/extractor/ParamRequestExtractorTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.extractor; 2 | 3 | import io.netty.handler.codec.http.FullHttpRequest; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import static org.hamcrest.CoreMatchers.is; 8 | import static org.hamcrest.CoreMatchers.nullValue; 9 | import static org.junit.Assert.assertThat; 10 | import static org.mockito.Mockito.mock; 11 | import static org.mockito.Mockito.when; 12 | 13 | public class ParamRequestExtractorTest { 14 | private ParamRequestExtractor extractor; 15 | private FullHttpRequest request; 16 | 17 | @Before 18 | public void setUp() throws Exception { 19 | extractor = new ParamRequestExtractor("param"); 20 | request = mock(FullHttpRequest.class); 21 | } 22 | 23 | @Test 24 | public void should_return_null_if_uri_has_no_parameter() { 25 | when(request.getUri()).thenReturn("/foo"); 26 | assertThat(extractor.extract(request), nullValue()); 27 | } 28 | 29 | @Test 30 | public void should_return_parameter_value_if_uri_has_same_parameter() { 31 | when(request.getUri()).thenReturn("/foo?param=bar"); 32 | assertThat(extractor.extract(request), is("bar")); 33 | } 34 | 35 | @Test 36 | public void should_extract_parameter_value_from_multiple_parameters() { 37 | when(request.getUri()).thenReturn("/foo?param=bar¶m2=bar2"); 38 | assertThat(extractor.extract(request), is("bar")); 39 | } 40 | 41 | @Test 42 | public void should_return_null_if_uri_has_no_same_parameter() { 43 | when(request.getUri()).thenReturn("/foo?param2=bar"); 44 | assertThat(extractor.extract(request), nullValue()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/extractor/XPathRequestExtractor.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.extractor; 2 | 3 | import com.github.dreamhead.moco.RequestExtractor; 4 | import io.netty.handler.codec.http.FullHttpRequest; 5 | import org.w3c.dom.Node; 6 | import org.w3c.dom.NodeList; 7 | 8 | import javax.xml.xpath.*; 9 | import java.util.List; 10 | 11 | import static com.google.common.collect.Lists.newArrayList; 12 | 13 | public class XPathRequestExtractor implements RequestExtractor { 14 | private final XmlExtractorHelper helper = new XmlExtractorHelper(); 15 | private final ContentRequestExtractor extractor = new ContentRequestExtractor(); 16 | private final XPathExpression xPathExpression; 17 | 18 | public XPathRequestExtractor(String xpath) { 19 | XPathFactory xPathfactory = XPathFactory.newInstance(); 20 | XPath target = xPathfactory.newXPath(); 21 | try { 22 | xPathExpression = target.compile(xpath); 23 | } catch (XPathExpressionException e) { 24 | throw new IllegalArgumentException(e); 25 | } 26 | } 27 | 28 | @Override 29 | public String[] extract(FullHttpRequest request) { 30 | try { 31 | NodeList list = (NodeList) xPathExpression.evaluate(helper.extractAsInputSource(request, extractor), XPathConstants.NODESET); 32 | List values = newArrayList(); 33 | for (int i = 0; i < list.getLength(); i++) { 34 | Node node = list.item(i); 35 | values.add(node.getNodeValue()); 36 | } 37 | return values.toArray(new String[values.size()]); 38 | } catch (XPathExpressionException e) { 39 | return new String[0]; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/internal/MocoServer.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.internal; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.*; 5 | import io.netty.channel.nio.NioEventLoopGroup; 6 | import io.netty.channel.socket.nio.NioServerSocketChannel; 7 | 8 | public class MocoServer { 9 | private EventLoopGroup bossGroup; 10 | private EventLoopGroup workerGroup; 11 | private ChannelFuture future; 12 | 13 | public MocoServer() { 14 | bossGroup = new NioEventLoopGroup(); 15 | workerGroup = new NioEventLoopGroup(); 16 | } 17 | 18 | public void start(final int port, ChannelHandler pipelineFactory) { 19 | ServerBootstrap bootstrap = new ServerBootstrap(); 20 | bootstrap.group(bossGroup, workerGroup) 21 | .channel(NioServerSocketChannel.class) 22 | .childHandler(pipelineFactory) 23 | .option(ChannelOption.TCP_NODELAY, true) 24 | .childOption(ChannelOption.SO_KEEPALIVE, true); 25 | 26 | try { 27 | future = bootstrap.bind(port).sync(); 28 | } catch (InterruptedException e) { 29 | throw new RuntimeException(e); 30 | } 31 | } 32 | 33 | public void stop() { 34 | doStop(); 35 | } 36 | 37 | private void doStop() { 38 | if (future != null) { 39 | future.channel().close().syncUninterruptibly(); 40 | future = null; 41 | } 42 | 43 | if (bossGroup != null) { 44 | bossGroup.shutdownGracefully(); 45 | bossGroup = null; 46 | } 47 | 48 | 49 | if (workerGroup != null) { 50 | workerGroup.shutdownGracefully(); 51 | workerGroup = null; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/mount/MountMatcher.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.mount; 2 | 3 | import com.github.dreamhead.moco.MocoConfig; 4 | import com.github.dreamhead.moco.RequestMatcher; 5 | import io.netty.handler.codec.http.FullHttpRequest; 6 | 7 | import java.io.File; 8 | 9 | import static com.google.common.base.Predicates.and; 10 | import static com.google.common.base.Strings.isNullOrEmpty; 11 | 12 | public class MountMatcher implements RequestMatcher { 13 | private final MountPathExtractor extractor; 14 | 15 | private final File dir; 16 | private final Iterable predicates; 17 | private final MountTo target; 18 | 19 | public MountMatcher(File dir, MountTo target, Iterable predicates) { 20 | this.dir = dir; 21 | this.predicates = predicates; 22 | this.target = target; 23 | this.extractor = new MountPathExtractor(target); 24 | } 25 | 26 | @Override 27 | public boolean match(FullHttpRequest request) { 28 | String relativePath = extractor.extract(request); 29 | return isTarget(relativePath) && new File(dir, relativePath).exists(); 30 | } 31 | 32 | @Override 33 | public RequestMatcher apply(final MocoConfig config) { 34 | if (config.isFor("uri")) { 35 | return new MountMatcher(this.dir, this.target.apply(config), this.predicates); 36 | } 37 | 38 | if (config.isFor("file")) { 39 | return new MountMatcher(new File(config.apply(this.dir.getName())), this.target, this.predicates); 40 | } 41 | 42 | return this; 43 | } 44 | 45 | private boolean isTarget(String relativePath) { 46 | return !isNullOrEmpty(relativePath) && and(predicates).apply(relativePath); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /moco-core/src/test/java/com/github/dreamhead/moco/MocoWebTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import org.apache.http.client.fluent.Request; 4 | import org.junit.Test; 5 | 6 | import java.io.IOException; 7 | 8 | import static com.github.dreamhead.moco.Moco.*; 9 | import static com.github.dreamhead.moco.RemoteTestUtils.remoteUrl; 10 | import static com.github.dreamhead.moco.RemoteTestUtils.root; 11 | import static com.github.dreamhead.moco.Runner.running; 12 | import static org.hamcrest.CoreMatchers.is; 13 | import static org.junit.Assert.assertThat; 14 | 15 | public class MocoWebTest extends AbstractMocoTest { 16 | @Test 17 | public void should_set_and_recognize_cookie() throws Exception { 18 | server.request(eq(cookie("loggedIn"), "true")).response(status(200)); 19 | server.response(cookie("loggedIn", "true"), status(302)); 20 | 21 | running(server, new Runnable() { 22 | @Override 23 | public void run() throws IOException { 24 | int statusBeforeLogin = Request.Get(root()).execute().returnResponse().getStatusLine().getStatusCode(); 25 | assertThat(statusBeforeLogin, is(302)); 26 | int statusAfterLogin = Request.Get(root()).execute().returnResponse().getStatusLine().getStatusCode(); 27 | assertThat(statusAfterLogin, is(200)); 28 | } 29 | }); 30 | } 31 | 32 | @Test 33 | public void should_redirect_to_expected_url() throws Exception { 34 | server.get(by(uri("/"))).response("foo"); 35 | server.get(by(uri("/redirectTo"))).redirectTo(root()); 36 | 37 | running(server, new Runnable() { 38 | @Override 39 | public void run() throws IOException { 40 | assertThat(helper.get(remoteUrl("/redirectTo")), is("foo")); 41 | } 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /moco-runner/src/test/java/com/github/dreamhead/moco/bootstrap/StartArgsTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.bootstrap; 2 | 3 | import org.junit.Test; 4 | 5 | import static com.github.dreamhead.moco.bootstrap.StartArgs.parse; 6 | import static org.hamcrest.CoreMatchers.is; 7 | import static org.junit.Assert.assertThat; 8 | 9 | public class StartArgsTest { 10 | @Test 11 | public void should_parse_start_arguments() { 12 | StartArgs args = parse("start", "-p", "12306", "-c", "foo.json"); 13 | assertThat(args.getPort(), is(12306)); 14 | assertThat(args.getConfigurationFile().get(), is("foo.json")); 15 | } 16 | 17 | @Test 18 | public void should_parse_settings() { 19 | StartArgs args = parse("start", "-p", "12306", "-g", "settings.json"); 20 | assertThat(args.getSettings().get(), is("settings.json")); 21 | } 22 | 23 | @Test 24 | public void should_parse_environment() { 25 | StartArgs args = parse("start", "-p", "12306", "-g", "setting.json", "-e", "foo"); 26 | assertThat(args.getEnv().get(), is("foo")); 27 | } 28 | 29 | @Test(expected = ParseArgException.class) 30 | public void should_set_at_least_config_or_settings() { 31 | parse("start", "-p", "12306"); 32 | } 33 | 34 | @Test(expected = ParseArgException.class) 35 | public void should_not_set_config_and_settings() { 36 | parse("start", "-p", "12306", "-c", "foo.json", "-g", "settings.json"); 37 | } 38 | 39 | @Test(expected = ParseArgException.class) 40 | public void should_not_set_environment_without_global_settings() { 41 | parse("start", "-p", "12306", "-e", "foo"); 42 | } 43 | 44 | @Test(expected = ParseArgException.class) 45 | public void should_not_set_environment_with_config() { 46 | parse("start", "-p", "12306", "-c", "foo.json", "-e", "foo"); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/matcher/JsonRequestMatcher.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.matcher; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.JsonNode; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.github.dreamhead.moco.MocoConfig; 7 | import com.github.dreamhead.moco.RequestExtractor; 8 | import com.github.dreamhead.moco.RequestMatcher; 9 | import com.github.dreamhead.moco.resource.Resource; 10 | import io.netty.handler.codec.http.FullHttpRequest; 11 | 12 | import java.io.IOException; 13 | 14 | public class JsonRequestMatcher implements RequestMatcher { 15 | private final RequestExtractor extractor; 16 | private final Resource resource; 17 | private final ObjectMapper mapper; 18 | 19 | public JsonRequestMatcher(RequestExtractor extractor, Resource resource) { 20 | this.extractor = extractor; 21 | this.resource = resource; 22 | this.mapper = new ObjectMapper(); 23 | } 24 | 25 | @Override 26 | public boolean match(FullHttpRequest request) { 27 | try { 28 | JsonNode requestNode = mapper.readTree(extractor.extract(request)); 29 | JsonNode resourceNode = mapper.readTree(resource.readFor(request)); 30 | return requestNode.equals(resourceNode); 31 | } catch (JsonProcessingException jpe) { 32 | return false; 33 | } catch (IOException e) { 34 | throw new RuntimeException(e); 35 | } 36 | } 37 | 38 | @Override 39 | public RequestMatcher apply(final MocoConfig config) { 40 | Resource appliedResource = this.resource.apply(config); 41 | if (appliedResource == this.resource) { 42 | return this; 43 | } 44 | 45 | return new JsonRequestMatcher(this.extractor, appliedResource); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /moco-core/src/test/java/com/github/dreamhead/moco/MocoJsonTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.IOException; 6 | 7 | import static com.github.dreamhead.moco.Moco.*; 8 | import static com.github.dreamhead.moco.RemoteTestUtils.root; 9 | import static com.github.dreamhead.moco.Runner.running; 10 | import static org.hamcrest.CoreMatchers.is; 11 | import static org.junit.Assert.assertThat; 12 | 13 | public class MocoJsonTest extends AbstractMocoTest { 14 | @Test 15 | public void should_return_content_based_on_jsonpath() throws Exception { 16 | server.request(eq(jsonPath("$.book[*].price"), "1")).response("jsonpath match success"); 17 | running(server, new Runnable() { 18 | @Override 19 | public void run() throws IOException { 20 | assertThat(helper.postContent(root(), "{\"book\":{\"price\":\"1\"}}"), 21 | is("jsonpath match success")); 22 | } 23 | }); 24 | } 25 | 26 | @Test 27 | public void should_match_extact_json() throws Exception { 28 | final String jsonContent = "{\"foo\":\"bar\"}"; 29 | server.request(json(text(jsonContent))).response("foo"); 30 | running(server, new Runnable() { 31 | @Override 32 | public void run() throws IOException { 33 | assertThat(helper.postContent(root(), jsonContent), is("foo")); 34 | } 35 | }); 36 | } 37 | 38 | @Test 39 | public void should_match_same_structure_json() throws Exception { 40 | server.request(json(text("{\"foo\":\"bar\"}"))).response("foo"); 41 | running(server, new Runnable() { 42 | @Override 43 | public void run() throws IOException { 44 | assertThat(helper.postContent(root(), "{\n\t\"foo\":\"bar\"\n}"), is("foo")); 45 | } 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /moco-runner/src/test/resources/template.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "uri": "/template" 5 | }, 6 | "response": { 7 | "text": { 8 | "template": "${req.method}" 9 | } 10 | } 11 | }, 12 | { 13 | "request": { 14 | "uri": "/file_template" 15 | }, 16 | "response": { 17 | "file": { 18 | "template": "src/test/resources/template.foo" 19 | } 20 | } 21 | }, 22 | { 23 | "request": { 24 | "uri": "/path_resource_template" 25 | }, 26 | "response": { 27 | "path_resource": { 28 | "template": "template.foo" 29 | } 30 | } 31 | }, 32 | { 33 | "request": { 34 | "uri": "/version_template" 35 | }, 36 | "response": { 37 | "version": { 38 | "template": "${req.version}" 39 | } 40 | } 41 | }, 42 | { 43 | "request": { 44 | "uri": "/header_template" 45 | }, 46 | "response": { 47 | "headers": { 48 | "foo": { 49 | "template" : "${req.headers['foo']}" 50 | } 51 | } 52 | } 53 | }, 54 | { 55 | "request": { 56 | "uri": "/cookie_template", 57 | "cookies": { 58 | "foo" : "GET" 59 | } 60 | }, 61 | "response": { 62 | "text" : "OK" 63 | } 64 | }, 65 | { 66 | "request": { 67 | "uri": "/cookie_template" 68 | }, 69 | "response": { 70 | "cookies": { 71 | "foo": { 72 | "template" : "${req.method}" 73 | } 74 | }, 75 | "status" : 302 76 | } 77 | } 78 | ] -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/parser/model/RequestSetting.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.parser.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.github.dreamhead.moco.RequestMatcher; 6 | import com.github.dreamhead.moco.parser.RequestMatcherFactory; 7 | import com.google.common.base.Objects; 8 | 9 | import java.util.Map; 10 | 11 | @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) 12 | public class RequestSetting { 13 | private final RequestMatcherFactory factory = new DynamicRequestMatcherFactory(); 14 | 15 | private TextContainer text; 16 | private TextContainer uri; 17 | private TextContainer file; 18 | @JsonProperty("path_resource") 19 | private TextContainer pathResource; 20 | private TextContainer method; 21 | private TextContainer version; 22 | 23 | private Map headers; 24 | private Map xpaths; 25 | @JsonProperty("json_paths") 26 | private Map jsonPaths; 27 | private Map queries; 28 | private Map cookies; 29 | private Map forms; 30 | 31 | @Override 32 | public String toString() { 33 | return Objects.toStringHelper(this).omitNullValues() 34 | .add("version", version) 35 | .add("URI", uri) 36 | .add("text", text) 37 | .add("file", file) 38 | .add("headers", headers) 39 | .add("xpaths", xpaths) 40 | .add("queries", queries) 41 | .add("cookies", cookies) 42 | .add("forms", forms) 43 | .toString(); 44 | } 45 | 46 | public RequestMatcher getRequestMatcher() { 47 | return factory.createRequestMatcher(this); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/handler/SequenceContentHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.handler; 2 | 3 | import com.github.dreamhead.moco.MocoConfig; 4 | import com.github.dreamhead.moco.ResponseHandler; 5 | import com.github.dreamhead.moco.resource.Resource; 6 | import com.google.common.base.Function; 7 | import com.google.common.collect.FluentIterable; 8 | import io.netty.buffer.ByteBuf; 9 | import io.netty.handler.codec.http.FullHttpRequest; 10 | 11 | import static com.google.common.collect.FluentIterable.from; 12 | import static com.google.common.collect.Lists.newArrayList; 13 | 14 | public class SequenceContentHandler extends AbstractContentResponseHandler { 15 | private final Resource[] resources; 16 | private int index; 17 | 18 | public SequenceContentHandler(final Resource[] resources) { 19 | this.resources = resources; 20 | } 21 | 22 | @Override 23 | protected void writeContentResponse(FullHttpRequest request, ByteBuf buffer) { 24 | buffer.writeBytes(resources[current()].readFor(request)); 25 | } 26 | 27 | private int current() { 28 | int current = this.index; 29 | if (++index >= resources.length) { 30 | index = resources.length - 1; 31 | } 32 | 33 | return current; 34 | } 35 | 36 | @Override 37 | public ResponseHandler apply(final MocoConfig config) { 38 | FluentIterable transformedResources = from(newArrayList(resources)).transform(applyConfig(config)); 39 | return new SequenceContentHandler(transformedResources.toArray(Resource.class)); 40 | } 41 | 42 | private Function applyConfig(final MocoConfig config) { 43 | return new Function() { 44 | @Override 45 | public Resource apply(Resource handler) { 46 | return handler.apply(config); 47 | } 48 | }; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/parser/model/MountSetting.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.parser.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 4 | import com.github.dreamhead.moco.mount.MountPredicate; 5 | import com.google.common.base.Function; 6 | 7 | import java.util.List; 8 | 9 | import static com.github.dreamhead.moco.MocoMount.exclude; 10 | import static com.github.dreamhead.moco.MocoMount.include; 11 | import static com.google.common.collect.ImmutableList.of; 12 | import static com.google.common.collect.Iterables.*; 13 | 14 | @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) 15 | public class MountSetting { 16 | private String dir; 17 | private String uri; 18 | private List includes = of(); 19 | private List excludes = of(); 20 | 21 | public String getDir() { 22 | return dir; 23 | } 24 | 25 | public String getUri() { 26 | return uri; 27 | } 28 | 29 | public MountPredicate[] getMountPredicates() { 30 | return toArray(toMountPredicates(), MountPredicate.class); 31 | } 32 | 33 | private Iterable toMountPredicates() { 34 | return unmodifiableIterable(concat( 35 | transform(includes, toInclude()), 36 | transform(excludes, toExclude()))); 37 | } 38 | 39 | private Function toInclude() { 40 | return new Function() { 41 | @Override 42 | public MountPredicate apply(String input) { 43 | return include(input); 44 | } 45 | }; 46 | } 47 | 48 | private Function toExclude() { 49 | return new Function() { 50 | @Override 51 | public MountPredicate apply(String input) { 52 | return exclude(input); 53 | } 54 | }; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/parser/model/ProxyContainerDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.parser.model; 2 | 3 | import com.fasterxml.jackson.core.JsonParser; 4 | import com.fasterxml.jackson.core.JsonProcessingException; 5 | import com.fasterxml.jackson.core.JsonToken; 6 | import com.fasterxml.jackson.databind.DeserializationContext; 7 | import com.fasterxml.jackson.databind.JsonDeserializer; 8 | 9 | import java.io.IOException; 10 | import java.util.Map; 11 | 12 | import static com.google.common.collect.Maps.newHashMap; 13 | 14 | public class ProxyContainerDeserializer extends JsonDeserializer { 15 | @Override 16 | public ProxyContainer deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { 17 | JsonToken currentToken = jp.getCurrentToken(); 18 | if (currentToken == JsonToken.VALUE_STRING) { 19 | return new ProxyContainer(jp.getText().trim(), null); 20 | } else if (currentToken == JsonToken.START_OBJECT) { 21 | JsonToken jsonToken = jp.nextToken(); 22 | if (jsonToken == JsonToken.FIELD_NAME) { 23 | return createFailoverProxy(jp); 24 | } 25 | } 26 | 27 | throw ctxt.mappingException(TextContainer.class, currentToken); 28 | } 29 | 30 | private ProxyContainer createFailoverProxy(JsonParser jp) throws IOException { 31 | Map fields = newHashMap(); 32 | fetchField(fields, jp); 33 | fetchField(fields, jp); 34 | return new ProxyContainer(fields.get("url"), fields.get("failover")); 35 | } 36 | 37 | private void fetchField(Map fields, JsonParser jp) throws IOException { 38 | String fieldName = jp.getText().trim(); 39 | jp.nextToken(); 40 | String fieldValue = jp.getText().trim(); 41 | jp.nextToken(); 42 | fields.put(fieldName.toLowerCase(), fieldValue); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/model/MessageFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.model; 2 | 3 | import com.google.common.base.Strings; 4 | import io.netty.handler.codec.http.*; 5 | 6 | import java.nio.charset.Charset; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | public class MessageFactory { 11 | private static void setContent(FullHttpMessage message, Message dumpedMessage) { 12 | String text = message.content().toString(Charset.defaultCharset()); 13 | if (!Strings.isNullOrEmpty(text)) { 14 | dumpedMessage.setContent(text); 15 | } 16 | } 17 | 18 | public static DefaultRequest createRequest(FullHttpRequest request) { 19 | DefaultRequest dumpedRequest = new DefaultRequest(); 20 | dumpedRequest.setVersion(request.getProtocolVersion().text()); 21 | setContent(request, dumpedRequest); 22 | dumpedRequest.setMethod(request.getMethod().name()); 23 | 24 | QueryStringDecoder decoder = new QueryStringDecoder(request.getUri()); 25 | for (Map.Entry> entry : decoder.parameters().entrySet()) { 26 | dumpedRequest.addQuery(entry.getKey(), entry.getValue().get(0)); 27 | } 28 | 29 | for (Map.Entry entry : request.headers()) { 30 | dumpedRequest.addHeader(entry.getKey(), entry.getValue()); 31 | } 32 | 33 | return dumpedRequest; 34 | } 35 | 36 | public static Response createResponse(FullHttpResponse response) { 37 | Response dumpedResponse = new Response(); 38 | dumpedResponse.setStatusCode(response.getStatus().code()); 39 | dumpedResponse.setVersion(response.getProtocolVersion().text()); 40 | for (Map.Entry entry : response.headers()) { 41 | dumpedResponse.addHeader(entry.getKey(), entry.getValue()); 42 | } 43 | setContent(response, dumpedResponse); 44 | return dumpedResponse; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/model/Message.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.model; 2 | 3 | import com.google.common.base.Objects; 4 | import com.google.common.base.Strings; 5 | 6 | import java.util.Map; 7 | 8 | import static com.google.common.collect.Maps.newHashMap; 9 | 10 | public abstract class Message { 11 | protected String version; 12 | protected String content; 13 | protected Map headers = newHashMap(); 14 | 15 | public void setVersion(String version) { 16 | this.version = version; 17 | } 18 | 19 | public void setContent(String content) { 20 | this.content = content; 21 | } 22 | 23 | public void addHeader(String key, String value) { 24 | this.headers.put(key, value); 25 | } 26 | 27 | public String getVersion() { 28 | return version; 29 | } 30 | 31 | public String getContent() { 32 | return content; 33 | } 34 | 35 | public Map getHeaders() { 36 | return this.headers; 37 | } 38 | 39 | public boolean match(Message that) { 40 | return doMatch(version, that.version) 41 | && doMatch(content, that.content) 42 | && doMatch(headers, that.headers); 43 | } 44 | 45 | protected boolean doMatch(Map thisField, Map thatField) { 46 | if (thisField == null || thisField.isEmpty()) { 47 | return true; 48 | } 49 | 50 | for (Map.Entry entry : thisField.entrySet()) { 51 | if (!doMatch(entry.getValue(), thatField.get(entry.getKey()))) { 52 | return false; 53 | } 54 | } 55 | 56 | return true; 57 | } 58 | 59 | protected boolean doMatch(String thisField, String thatField) { 60 | return Strings.isNullOrEmpty(thisField) || thisField.equals(thatField); 61 | } 62 | 63 | @Override 64 | public int hashCode() { 65 | return Objects.hashCode(version, content, headers); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/mount/MountHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.mount; 2 | 3 | import com.github.dreamhead.moco.MocoConfig; 4 | import com.github.dreamhead.moco.ResponseHandler; 5 | import com.github.dreamhead.moco.handler.AbstractContentResponseHandler; 6 | import com.github.dreamhead.moco.util.FileContentType; 7 | import io.netty.buffer.ByteBuf; 8 | import io.netty.handler.codec.http.FullHttpRequest; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | 13 | import static com.google.common.io.Files.toByteArray; 14 | 15 | public class MountHandler extends AbstractContentResponseHandler { 16 | private final MountPathExtractor extractor; 17 | 18 | private final File dir; 19 | private final MountTo target; 20 | 21 | public MountHandler(File dir, MountTo target) { 22 | this.dir = dir; 23 | this.target = target; 24 | this.extractor = new MountPathExtractor(target); 25 | } 26 | 27 | @Override 28 | protected void writeContentResponse(FullHttpRequest request, ByteBuf buffer) { 29 | try { 30 | buffer.writeBytes(toByteArray(targetFile(request))); 31 | } catch (IOException e) { 32 | throw new RuntimeException(e); 33 | } 34 | } 35 | 36 | private File targetFile(FullHttpRequest request) { 37 | String relativePath = extractor.extract(request); 38 | return new File(dir, relativePath); 39 | } 40 | 41 | @Override 42 | protected String getContentType(FullHttpRequest request) { 43 | return new FileContentType(targetFile(request).getName()).getContentType(); 44 | } 45 | 46 | @Override 47 | public ResponseHandler apply(final MocoConfig config) { 48 | if (config.isFor("uri")) { 49 | return new MountHandler(this.dir, this.target.apply(config)); 50 | } 51 | 52 | if (config.isFor("file")) { 53 | return new MountHandler(new File(config.apply(this.dir.getName())), this.target); 54 | } 55 | 56 | return this; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/runner/monitor/MonitorFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.runner.monitor; 2 | 3 | import com.github.dreamhead.moco.runner.*; 4 | import com.google.common.collect.Iterables; 5 | import org.apache.commons.io.monitor.FileAlterationListenerAdaptor; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.io.File; 10 | import java.util.List; 11 | 12 | import static com.google.common.collect.Lists.newArrayList; 13 | 14 | public class MonitorFactory { 15 | private static Logger logger = LoggerFactory.getLogger(MonitorFactory.class); 16 | 17 | public Runner createShutdownMonitor(final Runner runner, final int shutdownPort, final String shutdownKey) { 18 | return new MonitorRunner(runner, new ShutdownMonitor(shutdownPort, shutdownKey, new ShutdownListener() { 19 | @Override 20 | public void onShutdown() { 21 | runner.stop(); 22 | } 23 | })); 24 | } 25 | 26 | public FileMonitor createConfigurationMonitor(final File configuration, final FileRunner fileRunner) { 27 | return new FileMonitor(configuration, createListener(fileRunner)); 28 | } 29 | 30 | public Monitor createSettingMonitor(final File settingsFile, final Iterable configurationFiles, final FileRunner fileRunner) { 31 | List files = newArrayList(settingsFile); 32 | Iterables.addAll(files, configurationFiles); 33 | return new FilesMonitor(files, createListener(fileRunner)); 34 | } 35 | 36 | private FileAlterationListenerAdaptor createListener(final FileRunner fileRunner) { 37 | return new FileAlterationListenerAdaptor() { 38 | @Override 39 | public void onFileChange(File file) { 40 | logger.info("{} change detected.", file.getName()); 41 | try { 42 | fileRunner.restart(); 43 | } catch (Exception e) { 44 | logger.error("Fail to load configuration in {}.", file.getName()); 45 | logger.error(e.getMessage()); 46 | } 47 | } 48 | }; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/runner/monitor/FileMonitor.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.runner.monitor; 2 | 3 | import org.apache.commons.io.monitor.FileAlterationListener; 4 | import org.apache.commons.io.monitor.FileAlterationMonitor; 5 | import org.apache.commons.io.monitor.FileAlterationObserver; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.io.File; 10 | import java.io.FileFilter; 11 | 12 | public class FileMonitor implements Monitor { 13 | public static final int INTERVAL = 1000; 14 | private static Logger logger = LoggerFactory.getLogger(FileMonitor.class); 15 | 16 | private final FileAlterationMonitor monitor; 17 | private boolean running = false; 18 | 19 | public FileMonitor(File file, FileAlterationListener listener) { 20 | this.monitor = monitorFile(file, listener); 21 | } 22 | 23 | public synchronized void startMonitor() { 24 | try { 25 | monitor.start(); 26 | running = true; 27 | } catch (Exception e) { 28 | logger.error("Error found.", e); 29 | } 30 | } 31 | 32 | public synchronized void stopMonitor() { 33 | try { 34 | if (monitor != null && running) { 35 | monitor.stop(); 36 | running = false; 37 | } 38 | } catch (Exception e) { 39 | logger.error("Error found.", e); 40 | } 41 | } 42 | 43 | private FileAlterationMonitor monitorFile(File file, FileAlterationListener listener) { 44 | File parentFile = file.getParentFile(); 45 | File directory = (parentFile == null) ? new File(".") : parentFile; 46 | FileAlterationObserver observer = new FileAlterationObserver(directory, sameFile(file)); 47 | observer.addListener(listener); 48 | 49 | return new FileAlterationMonitor(INTERVAL, observer); 50 | } 51 | 52 | private FileFilter sameFile(final File file) { 53 | return new FileFilter() { 54 | @Override 55 | public boolean accept(File detectedFile) { 56 | return file.getName().equals(detectedFile.getName()); 57 | } 58 | }; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/runner/FileRunner.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.runner; 2 | 3 | import com.github.dreamhead.moco.bootstrap.StartArgs; 4 | 5 | import java.io.File; 6 | import java.io.FileInputStream; 7 | import java.io.FileNotFoundException; 8 | 9 | import static com.github.dreamhead.moco.runner.JsonRunner.newJsonRunnerWithStreams; 10 | import static com.google.common.collect.ImmutableList.of; 11 | 12 | public abstract class FileRunner implements Runner { 13 | protected final File file; 14 | protected final int port; 15 | private Runner runner; 16 | 17 | protected abstract Runner createRunner(); 18 | 19 | protected FileRunner(File file, int port) { 20 | this.file = file; 21 | this.port = port; 22 | this.runner = createRunner(); 23 | } 24 | 25 | public void restart() { 26 | this.runner.stop(); 27 | this.runner = createRunner(); 28 | this.runner.run(); 29 | } 30 | 31 | public Runner getRunner() { 32 | return runner; 33 | } 34 | 35 | @Override 36 | public void run() { 37 | this.runner.run(); 38 | } 39 | 40 | @Override 41 | public void stop() { 42 | this.runner.stop(); 43 | } 44 | 45 | public static FileRunner createConfigurationFileRunner(final File file, final int port) { 46 | return new FileRunner(file, port) { 47 | @Override 48 | protected Runner createRunner() { 49 | return newJsonRunnerWithStreams(of(toInputStream(file)), port); 50 | } 51 | }; 52 | } 53 | 54 | public static FileRunner createSettingFileRunner(final File settingsFile, final StartArgs startArgs) { 55 | return new FileRunner(settingsFile, startArgs.getPort()) { 56 | @Override 57 | protected Runner createRunner() { 58 | return new SettingRunner(toInputStream(file), startArgs); 59 | } 60 | }; 61 | } 62 | 63 | private static FileInputStream toInputStream(final File file) { 64 | try { 65 | return new FileInputStream(file); 66 | } catch (FileNotFoundException e) { 67 | throw new RuntimeException(e); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/parser/model/SessionSetting.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.parser.model; 2 | 3 | import static com.github.dreamhead.moco.MocoMount.to; 4 | 5 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 6 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 7 | import com.github.dreamhead.moco.HttpServer; 8 | import com.github.dreamhead.moco.RequestMatcher; 9 | import com.github.dreamhead.moco.ResponseHandler; 10 | import com.google.common.base.Objects; 11 | 12 | @JsonIgnoreProperties({ "description" }) 13 | @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) 14 | public class SessionSetting { 15 | private RequestSetting request; 16 | private ResponseSetting response; 17 | private String redirectTo; 18 | private MountSetting mount; 19 | 20 | private boolean isMount() { 21 | return this.mount != null; 22 | } 23 | 24 | private boolean isAnyResponse() { 25 | return request == null; 26 | } 27 | 28 | @Override 29 | public String toString() { 30 | return Objects.toStringHelper(this).omitNullValues().add("request", request).add("response", response).toString(); 31 | } 32 | 33 | private boolean isRedirectResponse() { 34 | return redirectTo != null; 35 | } 36 | 37 | private ResponseHandler getResponseHandler() { 38 | if (response == null) { 39 | throw new RuntimeException("No response specified"); 40 | } 41 | 42 | return response.getResponseHandler(); 43 | } 44 | 45 | private RequestMatcher getRequestMatcher() { 46 | if (request == null) { 47 | throw new RuntimeException("No request specified"); 48 | } 49 | 50 | return request.getRequestMatcher(); 51 | } 52 | 53 | public void bindTo(HttpServer server) { 54 | if (isMount()) { 55 | server.mount(mount.getDir(), to(mount.getUri()), mount.getMountPredicates()); 56 | } else if (isAnyResponse()) { 57 | server.response(getResponseHandler()); 58 | } else if (isRedirectResponse()) { 59 | server.request(getRequestMatcher()).redirectTo(redirectTo); 60 | } else { 61 | server.request(getRequestMatcher()).response(getResponseHandler()); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /moco-core/src/test/java/com/github/dreamhead/moco/handler/failover/DefaultRequestTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.handler.failover; 2 | 3 | import com.github.dreamhead.moco.model.DefaultRequest; 4 | import org.junit.Test; 5 | 6 | import static org.hamcrest.CoreMatchers.is; 7 | import static org.junit.Assert.assertThat; 8 | 9 | public class DefaultRequestTest { 10 | @Test 11 | public void should_be_match_if_request_is_same() { 12 | DefaultRequest request = new DefaultRequest(); 13 | request.setVersion("HTTP/1.1"); 14 | request.setMethod("POST"); 15 | request.setContent("proxy"); 16 | request.addHeader("Cookie", "loggedIn=true"); 17 | request.addHeader("Host", "localhost:12306"); 18 | 19 | assertThat(request.match(request), is(true)); 20 | } 21 | 22 | @Test 23 | public void should_be_match_if_failover_field_is_null() { 24 | DefaultRequest request = new DefaultRequest(); 25 | request.setVersion("HTTP/1.1"); 26 | request.setMethod("POST"); 27 | request.setContent("proxy"); 28 | request.addHeader("Cookie", "loggedIn=true"); 29 | request.addHeader("Host", "localhost:12306"); 30 | 31 | DefaultRequest failover = new DefaultRequest(); 32 | failover.setVersion(null); 33 | failover.setMethod("POST"); 34 | failover.setContent("proxy"); 35 | failover.addHeader("Cookie", "loggedIn=true"); 36 | failover.addHeader("Host", "localhost:12306"); 37 | 38 | assertThat(failover.match(failover), is(true)); 39 | } 40 | 41 | @Test 42 | public void should_be_match_even_if_target_request_has_more_headers() { 43 | DefaultRequest request = new DefaultRequest(); 44 | request.setVersion("HTTP/1.1"); 45 | request.setMethod("POST"); 46 | request.setContent("proxy"); 47 | request.addHeader("Cookie", "loggedIn=true"); 48 | request.addHeader("Host", "localhost:12306"); 49 | 50 | DefaultRequest failover = new DefaultRequest(); 51 | failover.setVersion("HTTP/1.1"); 52 | failover.setMethod("POST"); 53 | failover.setContent("proxy"); 54 | failover.addHeader("Host", "localhost:12306"); 55 | 56 | assertThat(failover.match(request), is(true)); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /moco-runner/src/test/java/com/github/dreamhead/moco/MocoMatchTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import org.apache.http.client.fluent.Content; 4 | import org.apache.http.client.fluent.Request; 5 | import org.junit.Test; 6 | 7 | import java.io.IOException; 8 | 9 | import static com.github.dreamhead.moco.RemoteTestUtils.remoteUrl; 10 | import static org.hamcrest.CoreMatchers.is; 11 | import static org.junit.Assert.assertThat; 12 | 13 | public class MocoMatchTest extends AbstractMocoStandaloneTest { 14 | @Test 15 | public void should_match_uri() throws IOException { 16 | runWithConfiguration("match.json"); 17 | assertThat(helper.get(remoteUrl("/bar/foo")), is("uri_match")); 18 | assertThat(helper.get(remoteUrl("/blah/foo")), is("uri_match")); 19 | } 20 | 21 | @Test 22 | public void should_match_text() throws IOException { 23 | runWithConfiguration("match.json"); 24 | assertThat(helper.postContent(remoteUrl("/text-match"), "barfoo"), is("text_match")); 25 | assertThat(helper.postContent(remoteUrl("/text-match"), "blahfoo"), is("text_match")); 26 | } 27 | 28 | @Test 29 | public void should_match_method() throws IOException { 30 | runWithConfiguration("match.json"); 31 | assertThat(helper.get(remoteUrl("/method-match")), is("method_match")); 32 | assertThat(helper.postContent(remoteUrl("/method-match"), "blah"), is("method_match")); 33 | } 34 | 35 | @Test 36 | public void should_match_header() throws IOException { 37 | runWithConfiguration("match.json"); 38 | 39 | Content jsonContent = Request.Get(remoteUrl("/header-match")).addHeader("Content-type", "application/json").execute().returnContent(); 40 | assertThat(jsonContent.asString(), is("header_match")); 41 | Content xmlContent = Request.Get(remoteUrl("/header-match")).addHeader("Content-type", "application/xml").execute().returnContent(); 42 | assertThat(xmlContent.asString(), is("header_match")); 43 | } 44 | 45 | @Test 46 | public void should_match_query() throws IOException { 47 | runWithConfiguration("match.json"); 48 | 49 | assertThat(helper.get(remoteUrl("/query-match?foo=bar")), is("query_match")); 50 | assertThat(helper.get(remoteUrl("/query-match?foo=blah")), is("query_match")); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/bootstrap/ShutdownTask.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.bootstrap; 2 | 3 | import com.github.dreamhead.moco.internal.MocoClient; 4 | import io.netty.channel.*; 5 | import io.netty.channel.socket.SocketChannel; 6 | import io.netty.handler.codec.string.StringEncoder; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.net.ConnectException; 11 | 12 | import static com.github.dreamhead.moco.bootstrap.ShutdownArgs.parse; 13 | 14 | public class ShutdownTask implements BootstrapTask { 15 | private static Logger logger = LoggerFactory.getLogger(ShutdownTask.class); 16 | 17 | private final MocoClient client = new MocoClient(); 18 | private final int defaultShutdownPort; 19 | private final String defaultShutdownKey; 20 | 21 | public ShutdownTask(int defaultShutdownPort, String defaultShutdownKey) { 22 | this.defaultShutdownPort = defaultShutdownPort; 23 | this.defaultShutdownKey = defaultShutdownKey; 24 | } 25 | 26 | @Override 27 | public void run(String[] args) { 28 | ShutdownArgs shutdownArgs = parse(args); 29 | client.run("127.0.0.1", shutdownArgs.getShutdownPort(defaultShutdownPort), new ChannelInitializer() { 30 | @Override 31 | protected void initChannel(SocketChannel ch) throws Exception { 32 | ChannelPipeline pipeline = ch.pipeline(); 33 | pipeline.addLast("encoder", new StringEncoder()); 34 | pipeline.addLast("handler", new ShutdownHandler()); 35 | } 36 | }); 37 | } 38 | 39 | private class ShutdownHandler extends ChannelInboundHandlerAdapter { 40 | @Override 41 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 42 | ctx.writeAndFlush(defaultShutdownKey + "\r\n").addListener(ChannelFutureListener.CLOSE); 43 | } 44 | 45 | @Override 46 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 47 | ctx.close(); 48 | 49 | if (ConnectException.class.isInstance(cause)) { 50 | logger.error("fail to shutdown, please specify correct shutdown port."); 51 | return; 52 | } 53 | 54 | throw new RuntimeException(cause); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /moco-runner/src/test/java/com/github/dreamhead/moco/MocoTemplateStandaloneTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import org.apache.http.Header; 4 | import org.apache.http.HttpVersion; 5 | import org.apache.http.ProtocolVersion; 6 | import org.apache.http.client.fluent.Request; 7 | import org.junit.Test; 8 | 9 | import java.io.IOException; 10 | 11 | import static com.github.dreamhead.moco.RemoteTestUtils.remoteUrl; 12 | import static org.hamcrest.CoreMatchers.is; 13 | import static org.junit.Assert.assertThat; 14 | 15 | public class MocoTemplateStandaloneTest extends AbstractMocoStandaloneTest { 16 | @Test 17 | public void should_return_content_with_template() throws IOException { 18 | runWithConfiguration("template.json"); 19 | assertThat(helper.get(remoteUrl("/template")), is("GET")); 20 | } 21 | 22 | @Test 23 | public void should_return_content_from_file_template() throws IOException { 24 | runWithConfiguration("template.json"); 25 | assertThat(helper.get(remoteUrl("/file_template")), is("GET")); 26 | } 27 | 28 | @Test 29 | public void should_return_content_from_path_resource_template() throws IOException { 30 | runWithConfiguration("template.json"); 31 | assertThat(helper.get(remoteUrl("/file_template")), is("GET")); 32 | } 33 | 34 | @Test 35 | public void should_return_version_from_template() throws IOException { 36 | runWithConfiguration("template.json"); 37 | ProtocolVersion version = Request.Get(remoteUrl("/version_template")).version(HttpVersion.HTTP_1_0).execute().returnResponse().getProtocolVersion(); 38 | assertThat(version.toString(), is("HTTP/1.0")); 39 | } 40 | 41 | @Test 42 | public void should_return_header_from_template() throws IOException { 43 | runWithConfiguration("template.json"); 44 | Header header = Request.Get(remoteUrl("/header_template")).addHeader("foo", "bar").execute().returnResponse().getFirstHeader("foo"); 45 | assertThat(header.getValue(), is("bar")); 46 | } 47 | 48 | @Test 49 | public void should_return_cookie_from_template() throws IOException { 50 | runWithConfiguration("template.json"); 51 | Request.Get(remoteUrl("/cookie_template")).execute(); 52 | String content = helper.get(remoteUrl("/cookie_template")); 53 | assertThat(content, is("OK")); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/runner/RunnerFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.runner; 2 | 3 | import com.github.dreamhead.moco.bootstrap.StartArgs; 4 | import com.github.dreamhead.moco.runner.monitor.Monitor; 5 | import com.github.dreamhead.moco.runner.monitor.MonitorFactory; 6 | 7 | import java.io.File; 8 | 9 | import static com.github.dreamhead.moco.runner.FileRunner.createConfigurationFileRunner; 10 | import static com.github.dreamhead.moco.runner.FileRunner.createSettingFileRunner; 11 | 12 | public class RunnerFactory { 13 | 14 | private final MonitorFactory monitorFactory = new MonitorFactory(); 15 | 16 | private final int defaultShutdownPort; 17 | private final String defaultShutdownKey; 18 | 19 | public RunnerFactory(int defaultShutdownPort, String defaultShutdownKey) { 20 | this.defaultShutdownPort = defaultShutdownPort; 21 | this.defaultShutdownKey = defaultShutdownKey; 22 | } 23 | 24 | public Runner createRunner(StartArgs startArgs) { 25 | Runner dynamicRunner = createDynamicRunner(startArgs); 26 | return monitorFactory.createShutdownMonitor(dynamicRunner, startArgs.getShutdownPort(defaultShutdownPort), defaultShutdownKey); 27 | } 28 | 29 | private Runner createDynamicRunner(StartArgs startArgs) { 30 | if (startArgs.hasConfigurationFile()) { 31 | return createDynamicConfigurationRunner(startArgs); 32 | } 33 | 34 | return createDynamicSettingRunner(startArgs); 35 | } 36 | 37 | private Runner createDynamicSettingRunner(StartArgs startArgs) { 38 | final File settingsFile = new File(startArgs.getSettings().get()); 39 | final FileRunner fileRunner = createSettingFileRunner(settingsFile, startArgs); 40 | final SettingRunner runner = (SettingRunner) fileRunner.getRunner(); 41 | Monitor fileMonitor = monitorFactory.createSettingMonitor(settingsFile, runner.getFiles(), fileRunner); 42 | return new MonitorRunner(fileRunner, fileMonitor); 43 | } 44 | 45 | private Runner createDynamicConfigurationRunner(StartArgs startArgs) { 46 | final File configuration = new File(startArgs.getConfigurationFile().get()); 47 | final FileRunner fileRunner = createConfigurationFileRunner(configuration, startArgs.getPort()); 48 | Monitor fileMonitor = monitorFactory.createConfigurationMonitor(configuration, fileRunner); 49 | return new MonitorRunner(fileRunner, fileMonitor); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | # [Moco](https://github.com/dreamhead/moco) [![Build Status](https://travis-ci.org/dreamhead/moco.png?branch=master)](https://travis-ci.org/dreamhead/moco) 8 | 9 | Moco is an easy setup stub framework, mainly focusing on testing and integration, inspired by Mock framework, e.g. [Mockito](http://code.google.com/p/mockito/), and [Playframework](http://www.playframework.com/) 10 | 11 | ## Latest Release 12 | * __0.8.1__ 13 | 14 | ## Why 15 | Integration, especially based on HTTP protocol, e.g. web service, REST etc, is wildly used in most of our development. 16 | 17 | In the old days, we just deployed another WAR to an application server, e.g. Jetty or Tomcat etc. As we all know, it's so boring to develop a WAR and deploy it to any application server, even if we use an embeded server. And the WAR needs to be reassembled even if we just want to change a little bit. 18 | 19 | ## Quick Start 20 | * Download [Standalone Moco Runner](http://repo1.maven.org/maven2/com/github/dreamhead/moco-runner/0.8.1/moco-runner-0.8.1-standalone.jar) 21 | * Write your own configuration file to describe your Moco server configuration as follow: 22 | ```json 23 | [ 24 | { 25 | "response" : 26 | { 27 | "text" : "Hello, Moco" 28 | } 29 | } 30 | ] 31 | ``` 32 | 33 | * Run Moco server 34 | ```shell 35 | java -jar moco-runner--standalone.jar start -p 12306 -c foo.json 36 | ``` 37 | 38 | * Now, open your favorite browser to visit http://localhost:12306 and you will see "Hello, Moco". 39 | 40 | ## Documents 41 | * More [Usages](moco-doc/usage.md) 42 | * Detailed [APIs](moco-doc/apis.md) 43 | * [Global Settings](moco-doc/global-settings.md) for multiple configuration files. 44 | 45 | ## Build 46 | Make sure you have JDK and Gradle installed. 47 | 48 | * Clone Moco 49 | 50 | ```shell 51 | git clone git@github.com:dreamhead/moco.git 52 | ``` 53 | * Build Moco 54 | 55 | ```shell 56 | gradle build 57 | ``` 58 | * Build uberjar 59 | 60 | ```shell 61 | gradle uberjar 62 | ``` 63 | * Check code before commit 64 | 65 | ```shell 66 | gradle check 67 | ``` 68 | 69 | ## Contributing 70 | Check out what you can help [here](moco-doc/plan.md) if you do not have any existing idea. 71 | 72 | ## Copyright and license 73 | Copyright 2013 ZHENG Ye 74 | 75 | Licensed under MIT License (the "License"); You may obtain a copy of the License in the LICENSE file, or at: 76 | 77 | https://raw.github.com/dreamhead/moco/master/MIT-LICENSE.txt 78 | -------------------------------------------------------------------------------- /moco-core/src/test/java/com/github/dreamhead/moco/MocoFileRootTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import com.github.dreamhead.moco.helper.MocoTestHelper; 4 | import org.apache.http.Header; 5 | import org.apache.http.client.fluent.Request; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | import java.io.IOException; 10 | 11 | import static com.github.dreamhead.moco.Moco.*; 12 | import static com.github.dreamhead.moco.MocoMount.to; 13 | import static com.github.dreamhead.moco.RemoteTestUtils.port; 14 | import static com.github.dreamhead.moco.RemoteTestUtils.remoteUrl; 15 | import static com.github.dreamhead.moco.RemoteTestUtils.root; 16 | import static com.github.dreamhead.moco.Runner.running; 17 | import static org.hamcrest.CoreMatchers.is; 18 | import static org.junit.Assert.assertThat; 19 | 20 | public class MocoFileRootTest { 21 | private HttpServer server; 22 | private MocoTestHelper helper; 23 | 24 | @Before 25 | public void setup() { 26 | helper = new MocoTestHelper(); 27 | server = httpserver(port(), fileRoot("src/test/resources")); 28 | } 29 | 30 | @Test 31 | public void should_config_file_root() throws Exception { 32 | server.response(file("foo.response")); 33 | 34 | running(server, new Runnable() { 35 | @Override 36 | public void run() throws IOException { 37 | assertThat(helper.get(root()), is("foo.response")); 38 | } 39 | }); 40 | } 41 | 42 | @Test 43 | public void should_return_header_from_file_root() throws Exception { 44 | server.response(header("foo", file("foo.response"))); 45 | 46 | running(server, new Runnable() { 47 | @Override 48 | public void run() throws IOException { 49 | Header header = Request.Get(root()).execute().returnResponse().getFirstHeader("foo"); 50 | assertThat(header.getValue(), is("foo.response")); 51 | } 52 | }); 53 | } 54 | 55 | @Test 56 | public void should_return_template_from_file_root() throws Exception { 57 | server.response(template(file("foo.response"))); 58 | 59 | running(server, new Runnable() { 60 | @Override 61 | public void run() throws IOException { 62 | assertThat(helper.get(root()), is("foo.response")); 63 | } 64 | }); 65 | } 66 | 67 | @Test 68 | public void should_mount_correctly() throws Exception { 69 | server.mount("test", to("/dir")); 70 | 71 | running(server, new Runnable() { 72 | @Override 73 | public void run() throws Exception { 74 | assertThat(helper.get(remoteUrl("/dir/dir.response")), is("response from dir")); 75 | } 76 | }); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/resource/reader/TemplateResourceReader.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.resource.reader; 2 | 3 | import com.github.dreamhead.moco.model.LazyRequest; 4 | import com.github.dreamhead.moco.resource.ContentResource; 5 | import freemarker.cache.StringTemplateLoader; 6 | import freemarker.core.ParseException; 7 | import freemarker.template.*; 8 | import io.netty.handler.codec.http.FullHttpRequest; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.io.ByteArrayOutputStream; 13 | import java.io.IOException; 14 | import java.io.OutputStreamWriter; 15 | import java.io.Writer; 16 | 17 | import static com.google.common.collect.ImmutableMap.of; 18 | 19 | public class TemplateResourceReader implements ContentResourceReader { 20 | private static final Logger logger = LoggerFactory.getLogger(TemplateResourceReader.class); 21 | private static final String TEMPLATE_NAME = "template"; 22 | 23 | static { 24 | try { 25 | freemarker.log.Logger.selectLoggerLibrary(freemarker.log.Logger.LIBRARY_NONE); 26 | } catch (ClassNotFoundException e) { 27 | throw new RuntimeException(e); 28 | } 29 | } 30 | 31 | private final ContentResource template; 32 | private final Configuration cfg; 33 | 34 | public TemplateResourceReader(ContentResource template) { 35 | this.template = template; 36 | this.cfg = new Configuration(); 37 | this.cfg.setObjectWrapper(new DefaultObjectWrapper()); 38 | this.cfg.setDefaultEncoding("UTF-8"); 39 | this.cfg.setIncompatibleImprovements(new Version(2, 3, 20)); 40 | } 41 | 42 | @Override 43 | public byte[] readFor(FullHttpRequest request) { 44 | StringTemplateLoader templateLoader = new StringTemplateLoader(); 45 | String templateSource = new String(this.template.readFor(request)); 46 | templateLoader.putTemplate(TEMPLATE_NAME, templateSource); 47 | cfg.setTemplateLoader(templateLoader); 48 | 49 | try { 50 | Template template = cfg.getTemplate("template"); 51 | ByteArrayOutputStream stream = new ByteArrayOutputStream(); 52 | Writer writer = new OutputStreamWriter(stream); 53 | template.process(of("req", new LazyRequest(request)), writer); 54 | return stream.toByteArray(); 55 | } catch (ParseException e) { 56 | logger.info("Template is {}", templateSource); 57 | throw new RuntimeException(e); 58 | } catch (IOException e) { 59 | throw new RuntimeException(e); 60 | } catch (TemplateException e) { 61 | throw new RuntimeException(e); 62 | } 63 | } 64 | 65 | @Override 66 | public String getContentType() { 67 | return template.getContentType(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /moco-core/src/main/java/com/github/dreamhead/moco/internal/MocoHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.internal; 2 | 3 | import com.github.dreamhead.moco.setting.BaseSetting; 4 | import com.google.common.eventbus.EventBus; 5 | import io.netty.channel.ChannelFuture; 6 | import io.netty.channel.ChannelFutureListener; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.channel.SimpleChannelInboundHandler; 9 | import io.netty.handler.codec.http.*; 10 | 11 | import java.util.List; 12 | 13 | public class MocoHandler extends SimpleChannelInboundHandler { 14 | private final EventBus eventBus = new EventBus(); 15 | 16 | private final List settings; 17 | private final BaseSetting anySetting; 18 | 19 | public MocoHandler(ActualHttpServer server) { 20 | this.settings = server.getSettings(); 21 | this.anySetting = server.getAnySetting(); 22 | this.eventBus.register(new MocoEventListener()); 23 | } 24 | 25 | @Override 26 | protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest message) throws Exception { 27 | eventBus.post(message); 28 | httpRequestReceived(ctx, message); 29 | } 30 | 31 | private void httpRequestReceived(final ChannelHandlerContext ctx, FullHttpRequest request) { 32 | HttpResponse response = getResponse(request); 33 | eventBus.post(response); 34 | ChannelFuture future = ctx.writeAndFlush(response); 35 | future.addListener(ChannelFutureListener.CLOSE); 36 | } 37 | 38 | private FullHttpResponse getResponse(FullHttpRequest request) { 39 | try { 40 | return doGetHttpResponse(request); 41 | } catch (RuntimeException e) { 42 | eventBus.post(e); 43 | return defaultResponse(request, HttpResponseStatus.BAD_REQUEST); 44 | } catch (Exception e) { 45 | eventBus.post(e); 46 | return defaultResponse(request, HttpResponseStatus.INTERNAL_SERVER_ERROR); 47 | } 48 | } 49 | 50 | private FullHttpResponse doGetHttpResponse(FullHttpRequest request) { 51 | FullHttpResponse response = defaultResponse(request, HttpResponseStatus.OK); 52 | 53 | for (BaseSetting setting : settings) { 54 | if (setting.match(request)) { 55 | setting.writeToResponse(request, response); 56 | return response; 57 | } 58 | } 59 | 60 | if (anySetting.match(request)) { 61 | anySetting.writeToResponse(request, response); 62 | return response; 63 | } 64 | 65 | return defaultResponse(request, HttpResponseStatus.BAD_REQUEST); 66 | } 67 | 68 | private FullHttpResponse defaultResponse(HttpRequest request, HttpResponseStatus status) { 69 | return new DefaultFullHttpResponse(request.getProtocolVersion(), status); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/parser/model/Dynamics.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.parser.model; 2 | 3 | import com.github.dreamhead.moco.Moco; 4 | import com.google.common.base.Predicate; 5 | 6 | import java.lang.reflect.Field; 7 | import java.lang.reflect.Method; 8 | import java.lang.reflect.Modifier; 9 | import java.util.Arrays; 10 | 11 | import static com.google.common.base.Predicates.and; 12 | import static com.google.common.base.Predicates.not; 13 | import static com.google.common.base.Predicates.or; 14 | 15 | public class Dynamics { 16 | protected Predicate isClassField() { 17 | return new Predicate() { 18 | @Override 19 | public boolean apply(Field field) { 20 | return "class".equals(field.getName()); 21 | } 22 | }; 23 | } 24 | 25 | protected Predicate isFinalField() { 26 | return new Predicate() { 27 | @Override 28 | public boolean apply(Field field) { 29 | return Modifier.isFinal(field.getModifiers()); 30 | } 31 | }; 32 | } 33 | 34 | protected Predicate fieldExist(final T target) { 35 | return new Predicate() { 36 | @Override 37 | public boolean apply(Field field) { 38 | try { 39 | return field.get(target) != null; 40 | } catch (IllegalAccessException e) { 41 | throw new RuntimeException(e); 42 | } 43 | } 44 | }; 45 | } 46 | 47 | protected Iterable getFields(Class clazz) { 48 | Field[] fields = clazz.getDeclaredFields(); 49 | for (Field field : fields) { 50 | field.setAccessible(true); 51 | } 52 | return Arrays.asList(fields); 53 | } 54 | 55 | protected Predicate isValidField(T target) { 56 | return and(not(or(isClassField(), isFinalField())), fieldExist(target)); 57 | } 58 | 59 | protected T invokeTarget(String name, Object value, Class clazz) { 60 | return invokeTarget(name, value, clazz, value.getClass()); 61 | } 62 | 63 | private T invokeTarget(String name, Object value, Class clazz, Class argClass) { 64 | try { 65 | Method method = Moco.class.getMethod(name, argClass); 66 | Object result = method.invoke(null, value); 67 | return clazz.cast(result); 68 | } catch (Exception e) { 69 | throw new RuntimeException(e); 70 | } 71 | } 72 | 73 | protected T invokeTarget(String name, int value, Class clazz) { 74 | return invokeTarget(name, value, clazz, Integer.TYPE); 75 | } 76 | 77 | protected T invokeTarget(String name, long value, Class clazz) { 78 | return invokeTarget(name, value, clazz, Long.TYPE); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /moco-core/src/test/java/com/github/dreamhead/moco/MocoXmlTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.IOException; 6 | 7 | import static com.github.dreamhead.moco.Moco.*; 8 | import static com.github.dreamhead.moco.RemoteTestUtils.root; 9 | import static com.github.dreamhead.moco.Runner.running; 10 | import static org.hamcrest.CoreMatchers.is; 11 | import static org.junit.Assert.assertThat; 12 | 13 | public class MocoXmlTest extends AbstractMocoTest { 14 | @Test 15 | public void should_return_content_based_on_xpath() throws Exception { 16 | server.request(eq(xpath("/request/parameters/id/text()"), "1")).response("foo"); 17 | server.request(eq(xpath("/request/parameters/id/text()"), "2")).response("bar"); 18 | 19 | running(server, new Runnable() { 20 | @Override 21 | public void run() throws IOException { 22 | assertThat(helper.postFile(root(), "foo.xml"), is("foo")); 23 | assertThat(helper.postFile(root(), "bar.xml"), is("bar")); 24 | } 25 | }); 26 | } 27 | 28 | @Test 29 | public void should_return_content_based_on_xpath_with_many_elements() throws Exception { 30 | server.request(eq(xpath("/request/parameters/id/text()"), "2")).response("bar"); 31 | 32 | running(server, new Runnable() { 33 | @Override 34 | public void run() throws IOException { 35 | assertThat(helper.postFile(root(), "foobar.xml"), is("bar")); 36 | } 37 | }); 38 | } 39 | 40 | @Test 41 | public void should_match_exact_xml() throws Exception { 42 | server.request(xml(file("src/test/resources/foo.xml"))).response("foo"); 43 | 44 | running(server, new Runnable() { 45 | @Override 46 | public void run() throws IOException { 47 | assertThat(helper.postFile(root(), "foo.xml"), is("foo")); 48 | } 49 | }); 50 | } 51 | 52 | @Test 53 | public void should_match_xml() throws Exception { 54 | server.request(xml(text("1"))).response("foo"); 55 | 56 | running(server, new Runnable() { 57 | @Override 58 | public void run() throws IOException { 59 | assertThat(helper.postFile(root(), "foo.xml"), is("foo")); 60 | } 61 | }); 62 | } 63 | 64 | @Test(expected = IOException.class) 65 | public void should_throw_exception_for_unknown_content() throws Exception { 66 | server.request(xml(text("1"))).response("foo"); 67 | 68 | running(server, new Runnable() { 69 | @Override 70 | public void run() throws IOException { 71 | helper.postContent(root(), "blah"); 72 | } 73 | }); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/runner/monitor/ShutdownMonitor.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.runner.monitor; 2 | 3 | import com.github.dreamhead.moco.internal.MocoServer; 4 | import com.google.common.io.CharStreams; 5 | import com.google.common.io.InputSupplier; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelInitializer; 8 | import io.netty.channel.ChannelPipeline; 9 | import io.netty.channel.SimpleChannelInboundHandler; 10 | import io.netty.channel.socket.SocketChannel; 11 | import io.netty.handler.codec.string.StringDecoder; 12 | 13 | import java.io.IOException; 14 | import java.io.Reader; 15 | import java.io.StringReader; 16 | 17 | public class ShutdownMonitor implements Monitor { 18 | private final MocoServer server = new MocoServer(); 19 | private final int shutdownPort; 20 | private final String shutdownKey; 21 | private final ShutdownListener shutdownListener; 22 | 23 | public ShutdownMonitor(int shutdownPort, String shutdownKey, ShutdownListener shutdownListener) { 24 | this.shutdownPort = shutdownPort; 25 | this.shutdownKey = shutdownKey; 26 | this.shutdownListener = shutdownListener; 27 | } 28 | 29 | public void startMonitor() { 30 | server.start(this.shutdownPort, new ChannelInitializer() { 31 | @Override 32 | protected void initChannel(SocketChannel ch) throws Exception { 33 | ChannelPipeline pipeline = ch.pipeline(); 34 | pipeline.addLast("decoder", new StringDecoder()); 35 | pipeline.addLast("handler", new ShutdownHandler()); 36 | } 37 | }); 38 | } 39 | 40 | public void stopMonitor() { 41 | server.stop(); 42 | } 43 | 44 | private class ShutdownHandler extends SimpleChannelInboundHandler { 45 | @Override 46 | protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { 47 | if (shouldShutdown(msg)) { 48 | shutdownListener.onShutdown(); 49 | shutdownMonitorSelf(); 50 | } 51 | } 52 | 53 | private void shutdownMonitorSelf() { 54 | new Thread(new Runnable() { 55 | @Override 56 | public void run() { 57 | stopMonitor(); 58 | } 59 | }).start(); 60 | } 61 | 62 | private boolean shouldShutdown(String message) { 63 | try { 64 | return shutdownKey.equals(CharStreams.readFirstLine(toSuppiler(message))); 65 | } catch (IOException e) { 66 | throw new RuntimeException(e); 67 | } 68 | } 69 | 70 | private InputSupplier toSuppiler(final String content) { 71 | return new InputSupplier() { 72 | @Override 73 | public Reader getInput() throws IOException { 74 | return new StringReader(content); 75 | } 76 | }; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/runner/SettingRunner.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.runner; 2 | 3 | import com.github.dreamhead.moco.bootstrap.StartArgs; 4 | import com.github.dreamhead.moco.parser.GlobalSettingParser; 5 | import com.github.dreamhead.moco.parser.model.GlobalSetting; 6 | import com.google.common.base.Function; 7 | import com.google.common.base.Optional; 8 | import com.google.common.base.Predicate; 9 | import com.google.common.collect.FluentIterable; 10 | 11 | import java.io.File; 12 | import java.io.FileInputStream; 13 | import java.io.FileNotFoundException; 14 | import java.io.InputStream; 15 | import java.util.List; 16 | 17 | import static com.github.dreamhead.moco.runner.JsonRunner.newJsonRunnerWithSetting; 18 | import static com.google.common.collect.FluentIterable.from; 19 | 20 | public class SettingRunner implements Runner { 21 | private static final GlobalSettingParser settingParser = new GlobalSettingParser(); 22 | private final int port; 23 | private final List globalSettings; 24 | private final Optional env; 25 | private JsonRunner jsonRunner; 26 | private final FluentIterable files; 27 | 28 | public SettingRunner(InputStream stream, StartArgs args) { 29 | this.port = args.getPort(); 30 | this.env = args.getEnv(); 31 | this.globalSettings = settingParser.parse(stream); 32 | this.files = from(globalSettings).transform(toFile()); 33 | } 34 | 35 | public Iterable getFiles() { 36 | return files; 37 | } 38 | 39 | public void run() { 40 | jsonRunner = newJsonRunnerWithSetting(from(globalSettings).filter(byEnv(this.env)).transform(toRunnerSetting()), port); 41 | jsonRunner.run(); 42 | } 43 | 44 | private Predicate byEnv(final Optional env) { 45 | 46 | return new Predicate() { 47 | @Override 48 | public boolean apply(GlobalSetting globalSetting) { 49 | return !env.isPresent() || env.get().equalsIgnoreCase(globalSetting.getEnv()); 50 | 51 | } 52 | }; 53 | } 54 | 55 | 56 | private Function toRunnerSetting() { 57 | return new Function() { 58 | @Override 59 | public RunnerSetting apply(GlobalSetting setting) { 60 | try { 61 | return new RunnerSetting(new FileInputStream(setting.getInclude()), setting.getContext(), setting.getFileRoot()); 62 | } catch (FileNotFoundException e) { 63 | throw new RuntimeException(e); 64 | } 65 | } 66 | }; 67 | } 68 | 69 | public void stop() { 70 | jsonRunner.stop(); 71 | } 72 | 73 | private Function toFile() { 74 | return new Function() { 75 | @Override 76 | public File apply(GlobalSetting input) { 77 | return new File(input.getInclude()); 78 | } 79 | }; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /moco-runner/src/test/java/com/github/dreamhead/moco/parser/GlobalSettingParserTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.parser; 2 | 3 | import com.github.dreamhead.moco.parser.model.GlobalSetting; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import java.io.InputStream; 8 | import java.util.List; 9 | 10 | import static org.hamcrest.CoreMatchers.is; 11 | import static org.junit.Assert.assertThat; 12 | 13 | public class GlobalSettingParserTest { 14 | 15 | private GlobalSettingParser parser; 16 | 17 | @Before 18 | public void setUp() throws Exception { 19 | parser = new GlobalSettingParser(); 20 | } 21 | 22 | @Test 23 | public void should_parse_settings_file() { 24 | InputStream stream = getResourceAsStream("multiple/settings.json"); 25 | List globalSettings = parser.parse(stream); 26 | 27 | assertThat(globalSettings.get(0).getInclude(), is("src/test/resources/multiple/foo.json")); 28 | assertThat(globalSettings.get(1).getInclude(), is("src/test/resources/multiple/bar.json")); 29 | } 30 | 31 | private InputStream getResourceAsStream(String filename) { 32 | return GlobalSettingParserTest.class.getClassLoader().getResourceAsStream(filename); 33 | } 34 | 35 | @Test 36 | public void should_parse_settings_file_with_context() { 37 | InputStream stream = getResourceAsStream("multiple/context-settings.json"); 38 | List globalSettings = parser.parse(stream); 39 | 40 | assertThat(globalSettings.get(0).getInclude(), is("src/test/resources/multiple/foo.json")); 41 | assertThat(globalSettings.get(0).getContext(), is("/foo")); 42 | assertThat(globalSettings.get(1).getInclude(), is("src/test/resources/multiple/bar.json")); 43 | assertThat(globalSettings.get(1).getContext(), is("/bar")); 44 | } 45 | 46 | @Test 47 | public void should_parse_setting_file_with_file_root() { 48 | InputStream stream = getResourceAsStream("multiple/fileroot-settings.json"); 49 | List globalSettings = parser.parse(stream); 50 | 51 | assertThat(globalSettings.get(0).getInclude(), is("src/test/resources/multiple/fileroot.json")); 52 | assertThat(globalSettings.get(0).getContext(), is("/fileroot")); 53 | assertThat(globalSettings.get(0).getFileRoot(), is("src/test/resources/")); 54 | } 55 | 56 | @Test 57 | public void should_parse_setting_file_with_env() { 58 | InputStream stream = getResourceAsStream("multiple/env-settings.json"); 59 | List globalSettings = parser.parse(stream); 60 | 61 | assertThat(globalSettings.get(0).getInclude(), is("src/test/resources/multiple/foo.json")); 62 | assertThat(globalSettings.get(0).getContext(), is("/foo")); 63 | assertThat(globalSettings.get(0).getEnv(), is("foo")); 64 | assertThat(globalSettings.get(1).getInclude(), is("src/test/resources/multiple/bar.json")); 65 | assertThat(globalSettings.get(1).getContext(), is("/bar")); 66 | assertThat(globalSettings.get(1).getEnv(), is("bar")); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /moco-runner/src/test/java/com/github/dreamhead/moco/runner/ShutdownMonitorRunnerTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.runner; 2 | 3 | import com.github.dreamhead.moco.bootstrap.ShutdownTask; 4 | import com.github.dreamhead.moco.bootstrap.StartArgs; 5 | import org.apache.http.conn.HttpHostConnectException; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | import java.io.IOException; 10 | import java.io.OutputStream; 11 | import java.net.ConnectException; 12 | import java.net.InetAddress; 13 | import java.net.Socket; 14 | 15 | import static com.github.dreamhead.moco.RemoteTestUtils.port; 16 | import static com.github.dreamhead.moco.RemoteTestUtils.root; 17 | import static org.hamcrest.CoreMatchers.is; 18 | import static org.junit.Assert.assertThat; 19 | import static org.junit.Assert.fail; 20 | 21 | public class ShutdownMonitorRunnerTest extends AbstractRunnerTest { 22 | 23 | private final String SHUTDOWN_MOCO_KEY = "_SHUTDOWN_MOCO_KEY"; 24 | private final int SHUTDOWN_PORT = 9527; 25 | 26 | @Before 27 | public void setup() { 28 | RunnerFactory factory = new RunnerFactory(SHUTDOWN_PORT, SHUTDOWN_MOCO_KEY); 29 | runner = factory.createRunner(new StartArgs(port(), SHUTDOWN_PORT, "src/test/resources/foo.json", null, null)); 30 | runner.run(); 31 | } 32 | 33 | @Test(expected = HttpHostConnectException.class) 34 | public void should_shutdown_runner_by_socket() throws IOException { 35 | try { 36 | assertThat(helper.get(root()), is("foo")); 37 | } catch (HttpHostConnectException e) { 38 | fail(e.getMessage()); 39 | } 40 | 41 | shutdownMoco(SHUTDOWN_PORT, SHUTDOWN_MOCO_KEY); 42 | waitChangeHappens(); 43 | helper.get(root()); 44 | } 45 | 46 | @Test(expected = ConnectException.class) 47 | public void should_stop_runner_directly() throws IOException { 48 | try { 49 | assertThat(helper.get(root()), is("foo")); 50 | } catch (HttpHostConnectException e) { 51 | fail(e.getMessage()); 52 | } 53 | 54 | runner.stop(); 55 | 56 | shutdownMoco(SHUTDOWN_PORT, SHUTDOWN_MOCO_KEY); 57 | } 58 | 59 | @Test(expected = HttpHostConnectException.class) 60 | public void should_stop_runner_via_shutdown_task() throws IOException { 61 | try { 62 | assertThat(helper.get(root()), is("foo")); 63 | } catch (HttpHostConnectException e) { 64 | fail(e.getMessage()); 65 | } 66 | 67 | ShutdownTask task = new ShutdownTask(SHUTDOWN_PORT, SHUTDOWN_MOCO_KEY); 68 | task.run(new String[0]); 69 | 70 | waitChangeHappens(); 71 | helper.get(root()); 72 | } 73 | 74 | private void shutdownMoco(int shutdownPort, String shutdownMocoKey) throws IOException { 75 | Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), shutdownPort); 76 | socket.setSoLinger(false, 0); 77 | 78 | OutputStream outputStream = socket.getOutputStream(); 79 | outputStream.write((shutdownMocoKey + "\r\n").getBytes()); 80 | 81 | outputStream.flush(); 82 | socket.close(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /moco-runner/src/main/java/com/github/dreamhead/moco/parser/HttpServerParser.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco.parser; 2 | 3 | import com.fasterxml.jackson.core.Version; 4 | import com.fasterxml.jackson.databind.JsonMappingException; 5 | import com.fasterxml.jackson.databind.Module; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; 8 | import com.fasterxml.jackson.databind.module.SimpleModule; 9 | import com.fasterxml.jackson.databind.type.TypeFactory; 10 | import com.github.dreamhead.moco.HttpServer; 11 | import com.github.dreamhead.moco.MocoConfig; 12 | import com.github.dreamhead.moco.internal.ActualHttpServer; 13 | import com.github.dreamhead.moco.parser.model.*; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | import java.io.IOException; 18 | import java.io.InputStream; 19 | import java.util.List; 20 | 21 | import static java.lang.String.format; 22 | 23 | public class HttpServerParser { 24 | private static Logger logger = LoggerFactory.getLogger(HttpServer.class); 25 | 26 | private final ObjectMapper mapper = new ObjectMapper(); 27 | private final TypeFactory factory = TypeFactory.defaultInstance(); 28 | 29 | public HttpServerParser() { 30 | Module textContainerModule = new SimpleModule("TextContainerModule", 31 | new Version(1, 0, 0, null, null, null)) 32 | .addDeserializer(TextContainer.class, new TextContainerDeserializer()); 33 | Module proxyContainerModule = new SimpleModule("ProxyContainerModule", 34 | new Version(1, 0, 0, null, null, null)) 35 | .addDeserializer(ProxyContainer.class, new ProxyContainerDeserializer()); 36 | mapper.registerModule(textContainerModule); 37 | mapper.registerModule(proxyContainerModule); 38 | } 39 | 40 | public HttpServer parseServer(InputStream is, int port, MocoConfig... configs) { 41 | return createHttpServer(readSessions(is), port, configs); 42 | } 43 | 44 | private List readSessions(InputStream is) { 45 | try { 46 | return mapper.readValue(is, factory.constructCollectionType(List.class, SessionSetting.class)); 47 | } catch (UnrecognizedPropertyException e) { 48 | logger.info("Unrecognized field: {}", e.getMessage()); 49 | throw new RuntimeException(format("Unrecognized field [ %s ], please check!", e.getUnrecognizedPropertyName())); 50 | } catch (JsonMappingException e) { 51 | logger.info("{} {}", e.getMessage(), e.getPathReference()); 52 | throw new RuntimeException(e); 53 | } catch (IOException e) { 54 | throw new RuntimeException(e); 55 | } 56 | } 57 | 58 | private HttpServer createHttpServer(List sessionSettings, int port, MocoConfig... configs) { 59 | HttpServer server = new ActualHttpServer(port, configs); 60 | for (SessionSetting session : sessionSettings) { 61 | logger.debug("Parse session: {}", session); 62 | 63 | session.bindTo(server); 64 | } 65 | 66 | return server; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /moco-core/src/test/java/com/github/dreamhead/moco/MocoMountTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import org.apache.http.client.HttpResponseException; 4 | import org.junit.Test; 5 | 6 | import java.io.IOException; 7 | 8 | import static com.github.dreamhead.moco.MocoMount.*; 9 | import static com.github.dreamhead.moco.RemoteTestUtils.remoteUrl; 10 | import static com.github.dreamhead.moco.Runner.running; 11 | import static org.hamcrest.CoreMatchers.is; 12 | import static org.junit.Assert.assertThat; 13 | 14 | public class MocoMountTest extends AbstractMocoTest { 15 | 16 | private static final String MOUNT_DIR = "src/test/resources/test"; 17 | 18 | @Test 19 | public void should_mount_dir_to_uri() throws Exception { 20 | server.mount(MOUNT_DIR, to("/dir")); 21 | 22 | running(server, new Runnable() { 23 | @Override 24 | public void run() throws IOException { 25 | assertThat(helper.get(remoteUrl("/dir/dir.response")), is("response from dir")); 26 | } 27 | }); 28 | } 29 | 30 | @Test(expected = HttpResponseException.class) 31 | public void should_return_bad_request_for_nonexistence_file() throws Exception { 32 | server.mount(MOUNT_DIR, to("/dir")); 33 | 34 | running(server, new Runnable() { 35 | @Override 36 | public void run() throws IOException { 37 | helper.get(remoteUrl("/dir/unknown.response")); 38 | } 39 | }); 40 | } 41 | 42 | @Test 43 | public void should_return_inclusion_file() throws Exception { 44 | server.mount(MOUNT_DIR, to("/dir"), include("*.response")); 45 | 46 | running(server, new Runnable() { 47 | @Override 48 | public void run() throws IOException { 49 | assertThat(helper.get(remoteUrl("/dir/dir.response")), is("response from dir")); 50 | } 51 | }); 52 | } 53 | 54 | @Test(expected = HttpResponseException.class) 55 | public void should_not_return_non_inclusion_file() throws Exception { 56 | server.mount(MOUNT_DIR, to("/dir"), include("*.response")); 57 | 58 | running(server, new Runnable() { 59 | @Override 60 | public void run() throws IOException { 61 | helper.get(remoteUrl("/dir/foo.bar")); 62 | } 63 | }); 64 | } 65 | 66 | @Test(expected = HttpResponseException.class) 67 | public void should_not_return_exclusion_file() throws Exception { 68 | server.mount(MOUNT_DIR, to("/dir"), exclude("*.response")); 69 | 70 | running(server, new Runnable() { 71 | @Override 72 | public void run() throws IOException { 73 | helper.get(remoteUrl("/dir/dir.response")); 74 | } 75 | }); 76 | } 77 | 78 | @Test 79 | public void should_return_non_exclusion_file() throws Exception { 80 | server.mount(MOUNT_DIR, to("/dir"), exclude("*.response")); 81 | 82 | running(server, new Runnable() { 83 | @Override 84 | public void run() throws IOException { 85 | assertThat(helper.get(remoteUrl("/dir/foo.bar")), is("foo.bar")); 86 | } 87 | }); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /moco-core/src/test/java/com/github/dreamhead/moco/MocoContextTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamhead.moco; 2 | 3 | import com.github.dreamhead.moco.helper.MocoTestHelper; 4 | import org.apache.http.client.HttpResponseException; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | 8 | import java.io.IOException; 9 | 10 | import static com.github.dreamhead.moco.Moco.*; 11 | import static com.github.dreamhead.moco.MocoMount.to; 12 | import static com.github.dreamhead.moco.RemoteTestUtils.port; 13 | import static com.github.dreamhead.moco.RemoteTestUtils.remoteUrl; 14 | import static com.github.dreamhead.moco.RemoteTestUtils.root; 15 | import static com.github.dreamhead.moco.Runner.running; 16 | import static org.hamcrest.CoreMatchers.is; 17 | import static org.junit.Assert.assertThat; 18 | 19 | public class MocoContextTest { 20 | private static final String MOUNT_DIR = "src/test/resources/test"; 21 | 22 | private HttpServer server; 23 | private MocoTestHelper helper; 24 | 25 | @Before 26 | public void setUp() throws Exception { 27 | helper = new MocoTestHelper(); 28 | server = httpserver(port(), context("/context")); 29 | } 30 | 31 | @Test 32 | public void should_config_context() throws Exception { 33 | server.get(by(uri("/foo"))).response("foo"); 34 | 35 | running(server, new Runnable() { 36 | @Override 37 | public void run() throws Exception { 38 | assertThat(helper.get(remoteUrl("/context/foo")), is("foo")); 39 | } 40 | }); 41 | } 42 | 43 | @Test 44 | public void should_mount_correctly() throws Exception { 45 | server.mount(MOUNT_DIR, to("/dir")); 46 | 47 | running(server, new Runnable() { 48 | @Override 49 | public void run() throws IOException { 50 | assertThat(helper.get(remoteUrl("/context/dir/dir.response")), is("response from dir")); 51 | } 52 | }); 53 | } 54 | 55 | @Test 56 | public void should_have_context_even_if_there_is_no_context_configured() throws Exception { 57 | server.response("foo"); 58 | 59 | running(server, new Runnable() { 60 | @Override 61 | public void run() throws Exception { 62 | String content = helper.get(remoteUrl("/context")); 63 | assertThat(content, is("foo")); 64 | } 65 | }); 66 | } 67 | 68 | @Test(expected = HttpResponseException.class) 69 | public void should_throw_exception_without_context() throws Exception { 70 | server.request(by("foo")).response("foo"); 71 | 72 | running(server, new Runnable() { 73 | @Override 74 | public void run() throws Exception { 75 | helper.postContent(root(), "foo"); 76 | } 77 | }); 78 | } 79 | 80 | @Test(expected = HttpResponseException.class) 81 | public void should_throw_exception_without_context_for_any_response_handler() throws Exception { 82 | server.response("foo"); 83 | 84 | running(server, new Runnable() { 85 | @Override 86 | public void run() throws Exception { 87 | helper.get(root()); 88 | } 89 | }); 90 | } 91 | } 92 | --------------------------------------------------------------------------------