Skip to content

Latest commit

 

History

History
3619 lines (2884 loc) · 59.9 KB

apis.md

File metadata and controls

3619 lines (2884 loc) · 59.9 KB

HTTP(s) APIs

Moco mainly focuses on server configuration. There are only two kinds of API right now: Request and Response.

That means if we get the expected request and then return our response. Now, you can see a Moco reference in details.

WARNING the json configuration below is just a snippet for one pair of request and response, instead of the whole configuration file.

Table of Contents

Composite Java API Design

Moco Java API is designed in functional fashion which means you can composite any request or response easily.

server.request(and(by(uri("/target")), by(version(VERSION_1_0)))).response(with(text("foo")), header("Content-Type", "text/html"));

Description as comment

@Since 0.7

In all JSON APIs, you can use description to describe what is this session for. It's just used as comment, which will be ignored in runtime.

[
    {
        "description": "any response",
        "response": {
            "text": "foo"
        }
    }
]

Request

Content

@Since 0.7

If you want to response according to request content, Moco server can be configured as following:

  • Java API
server.request(by("foo")).response("bar");
  • JSON
{
  "request" :
    {
      "text" : "foo"
    },
  "response" :
    {
      "text" : "bar"
    }
}

If request content is too large, you can put it in a file:

  • Java API
server.request(by(file("foo.request"))).response("bar");
  • JSON
{
  "request" :
    {
      "file" : "foo.request"
    },
  "response" :
    {
      "text" : "bar"
    }
}

@Since 1.2.0

You can also match binary request directly.

server.request(binary(new byte[] {1, 2, 3})).response("bar");

InputStream is also supported in binary.

server.request(binary(new ByteArrayInputStream(new byte[]{1, 2, 3}))).response("bar");

URI

@Since 0.7

If request uri is your major focus, Moco server could be like this:

  • Java API
server.request(by(uri("/foo"))).response("bar");
  • JSON
{
  "request" :
    {
      "uri" : "/foo"
    },
  "response" :
    {
      "text" : "bar"
    }
}

Query Parameter

@Since 0.7

Sometimes, your request has parameters in query:

  • Java API
server.request(and(by(uri("/foo")), eq(query("param"), "blah"))).response("bar");
  • JSON
{
  "request" :
    {
      "uri" : "/foo",
      "queries" : 
        {
          "param" : "blah"
        }
    },
  "response" :
    {
      "text" : "bar"
    }
}

HTTP Method

@Since 0.7

It's easy to response based on specified HTTP method:

  • Java API
server.get(by(uri("/foo"))).response("bar");
  • JSON
{
  "request" :
    {
      "method" : "get",
      "uri" : "/foo"
    },
  "response" :
    {
      "text" : "bar"
    }
}

Also for POST method:

  • Java API
server.post(by("foo")).response("bar");
  • JSON
{
  "request" :
    {
      "method" : "post",
      "text" : "foo"
    },
  "response" :
    {
      "text" : "bar"
    }
}

and PUT method:

  • Java API
server.put(by("foo")).response("bar");
  • JSON
{
  "request" :
    {
      "method" : "put",
      "text" : "foo"
    },
  "response" :
    {
      "text" : "bar"
    }
}

and DELETE method:

  • Java API
server.delete(by(uri("/foo"))).response(status(200));
  • JSON
{
  "request" :
    {
      "method" : "delete",
      "uri" : "/foo"
    },
  "response" :
    {
      "status" : "200"
    }
}

If you do need other method, feel free to specify method directly:

  • Java API
server.request(by(method("HEAD"))).response("bar");
  • JSON
{
  "request" :
    {
      "method" : "HEAD"
    },
  "response" :
    {
      "text" : "bar"
    }
}

Version

@Since 0.7

We can return different response for different HTTP version:

  • Java API
server.request(by(version("HTTP/1.0"))).response("version");
  • JSON
{
  "request": 
    {
      "version": "HTTP/1.0"
    },
  "response": 
    {
      "text": "version"
    }
}

Header

@Since 0.7

We will focus HTTP header at times:

  • Java API
server.request(eq(header("foo"), "bar")).response("blah")
  • JSON
{
  "request" :
    {
      "method" : "post",
      "headers" : 
      {
        "content-type" : "application/json"
      }
    },
  "response" :
    {
      "text" : "bar"
    }
}

Cookie

@Since 0.7

Cookie is widely used in web development.

  • Java API
server.request(eq(cookie("loggedIn"), "true")).response(status(200));
  • JSON
{
  "request" :
    {
      "uri" : "/cookie",
      "cookies" :
        {
          "login" : "true"
        }
    },
  "response" :
    {
      "text" : "success"
    }
}

Form

@Since 0.7

In web development, form is often used to submit information to server side.

  • Java API
server.post(eq(form("name"), "foo")).response("bar");
  • JSON
{
  "request" :
    {
      "method" : "post",
      "forms" :
        {
          "name" : "foo"
        }
    },
  "response" : 
    {
      "text" : "bar"
    }
}

XML

@Since 0.7

XML is a popular format for Web Services. When a request is in XML, only the XML structure is important in most cases and whitespace can be ignored. The xml operator can be used for this case.

  • Java API
server.request(xml(text("<request><parameters><id>1</id></parameters></request>"))).response("foo");
  • JSON
{
  "request": 
    {
      "uri": "/xml",
      "text": 
        {
          "xml": "<request><parameters><id>1</id></parameters></request>"
        }
    },
  "response": 
    {
      "text": "foo"
    }
}

NOTE: Please escape the quote in text.

The large request can be put into a file:

{
   "request": 
     {
        "uri": "/xml",
        "file": 
          {
            "xml": "your_file.xml"
          }
    },
  "response": 
    {
      "text": "foo"
    }
}

XPath

@Since 0.7

For the XML/HTML request, Moco allows us to match request with XPath.

  • Java API
server.request(eq(xpath("/request/parameters/id/text()"), "1")).response("bar");
  • JSON
{
  "request" :
    {
      "method" : "post",
      "xpaths" : 
        {
          "/request/parameters/id/text()" : "1"
        }
    },
  "response" :
    {
      "text" : "bar"
    }
}

XML Struct

@Since 1.3.0

Moco also allows you to match an XML request only for same struct no matter what actual content is.

  • Java API
server.request(struct(xml("<foo>1</foo>")).response("response_for_xml_struct_request");
  • JSON
{
  "request":
  {
    "struct":
    {
      "xml" : "<foo>1</foo>"
    }
  },
  "response":
  {
    "text": "response_for_xml_struct_request"
  }
}

JSON Request

JSON Text

@Since 0.7

Json is rising with RESTful style architecture. Just like XML, in the most case, only JSON structure is important, so json operator can be used.

  • Java API
server.request(json(text("{\"foo\":\"bar\"}"))).response("foo");

@Since 0.12.0 json will return a resource from 1.3.0.

server.request(by(json(text("{\"foo\":\"bar\"}")))).response("foo");

Note that this functionality is implemented in Jackson, please make sure your POJO is written in Jackson acceptable format.

  • JSON
{
  "request":
    {
      "uri": "/json",
      "text":
        {
          "json": "{\"foo\":\"bar\"}"
        }
    },
  "response":
    {
      "text": "foo"
    }
}

NOTE: Please escape the quote in text.

JSON Shortcut

If the response is JSON, we don't need to write JSON text with escape character in code.

@Since 0.10.2

You can give a POJO to Java API, it will be converted JSON text.

server.request(json(pojo)).response("foo");

@Since 0.12.0 json will return a resource from 1.3.0.

server.request(by(json(pojo))).response("foo");

@Since 0.9.2

As you have seen, it is so boring to write json with escape character, especially in json configuration. So you can try the json shortcut. The upper case could be rewritten as following:

  • JSON
{
    "request": {
        "uri": "/json",
        "json": {
            "foo": "bar"
        }
    },
    "response": {
        "text": "foo"
    }
}

JSON File

@Since 0.7

The large request can be put into a file:

  • Java API
server.request(json(file("your_file.json"))).response("foo");
  • JSON
{
  "request":
    {
      "uri": "/json",
      "file":
        {
          "json": "your_file.json"
        }
    },
  "response":
    {
      "text": "foo"
    }
}

JSONPath

@Since 0.7

For the JSON request, Moco allows us to match request with JSONPath.

  • Java API
server.request(eq(jsonPath("$.book[*].price"), "1")).response("response_for_json_path_request");
  • JSON
{
  "request":
    {
      "json_paths":
        {
          "$.book[*].price": "1"
	    }
    },
  "response":
    {
      "text": "response_for_json_path_request"
    }
}

JSON Struct

@Since 1.3.0

Moco also allows you to match a JSON request only for same struct no matter what actual content is.

  • Java API
server.request(struct(json("{\"foo\":1}")).response("response_for_json_struct_request");
  • JSON
{
  "request":
  {
    "struct":
    {
      "json" : {
        "foo" :1
      }
    }
  },
  "response":
  {
    "text": "response_for_json_struct_request"
  }
}

Operator

Moco also supports some operators which helps you write your expectation easily.

Match

@Since 0.7

You may want to match your request with regular expression, match could be your helper:

  • Java API
server.request(match(uri("/\\w*/foo"))).response("bar");
  • JSON
{
  "request":
    {
      "uri":
        {
          "match": "/\\w*/foo"
        }
    },
  "response":
    {
      "text": "bar"
    }
}

Moco is implemented by Java regular expression, you can refer here for more details.

Starts With

@Since 0.9.2

starsWith operator can help you decide if the request information starts with a piece of text.

  • Java API
server.request(startsWith(uri("/foo"))).response("bar");
  • JSON
{
  "request":
    {
      "uri":
        {
          "startsWith": "/foo"
        }
    },
  "response":
    {
      "text": "bar"
    }
}

Ends With

@Since 0.9.2

endsWith operator can help you decide if the request information ends with a piece of text.

  • Java API
server.request(endsWith(uri("foo"))).response("bar");
  • JSON
{
  "request":
    {
      "uri":
        {
          "endsWith": "foo"
        }
    },
  "response":
    {
      "text": "bar"
    }
}

Contain

@Since 0.9.2

contain operator helps you know whether the request information contains a piece of text.

  • Java API
server.request(contain(uri("foo"))).response("bar");
  • JSON
{
  "request":
    {
      "uri":
        {
          "contain": "foo"
        }
    },
  "response":
    {
      "text": "bar"
    }
}

Exist

@Since 0.9.2

exist operator is used to decide whether the request information exists.

  • Java API
server.request(exist(header("foo"))).response("bar");
  • JSON
{
  "request":
    {
      "headers": {
        "foo": {
          "exist" : "true"
        }
      }
    },
  "response":
    {
      "text": "bar"
    }
}

For JSON API, you can decide whether the information does not exist.

  • JSON
{
  "request":
    {
      "headers": {
        "foo": {
          "exist" : "false"
        }
      }
    },
  "response":
    {
      "text": "bar"
    }
}

Path

@Since 1.4.0

path operator is provided to match uri with path variable.

  • Java API
server.request(path(uri("/path/{path}/sub/{sub}"))).response("bar");
  • JSON
{
  "request": 
  {
    "uri": 
    {
      "path": "/path/{path}/sub/{sub}"
    }
  },
  "response": 
  {
    "text": "sub"
  }
}

Conditional

@Since 1.3.0

If you want to implement your own matcher, you can write with conditional API which is supported in Java code.

server.request(conditional(request -> request.getContent().toString().equals("foo"))).response("foo");

Response

Content

@Since 0.7

As you have seen in previous example, response with content is pretty easy.

  • Java API
server.request(by("foo")).response("bar");
  • JSON
{
  "request" :
    {
      "text" : "foo"
    },
  "response" :
    {
      "text" : "bar"
    }
}

The same as request, you can response with a file if content is too large to put in a string.

  • Java API
server.request(by("foo")).response(file("bar.response"));
  • JSON
{
  "request" :
    {
      "text" : "foo"
    },
  "response" :
    {
      "file" : "bar.response"
    }
}

@Since 0.10.1

You can specify file charset if you want to see it in correct encoding in console.

  • Java API
server.response(file("src/test/resources/gbk.response", Charset.forName("GBK")));
  • JSON
[
  {
    "response":
    {
      "file":
      {
        "name": "gbk.response",
        "charset": "GBK"
      }
    }
  }
]

Charset can also be used in path resource.

  • Java API
server.response(pathResource("src/test/resources/gbk.response", Charset.forName("GBK")));
  • JSON
[
  {
    "response":
    {
      "path_resource":
      {
        "name": "gbk.response",
        "charset": "GBK"
      }
    }
  }
]

@Since 1.2.0

You can also write binary response directly.

server.response(binary(new byte[] {1, 2, 3}));

@Since 1.2.0

If your response need to be transformed for some reason, e.g. encryption, you can transform your content.

server.response(text("hello").transform(raw -> {
    byte[] transformed = new byte[raw.length];
    for (int i = 0; i < raw.length; i++) {
        transformed[i] = (byte) (raw[i] + 1);
    }
    return transformed;
}));

@Since 1.2.0

If you need your own specific response content, you can write your own code with lambda.

server.response(text((request) -> "foo"));
server.response(binary((request) -> new byte[] {1, 2, 3}));

Currently, this function uses com.github.dreamhead.moco.Request as argument which provide content only. If you need HTTP information in HTTP scenario, you can cast request to com.github.dreamhead.moco.HttpRequest.

server.response(text((request) -> ((HttpRequest)request).getUri()));

Status Code

@Since 0.7

Moco also supports HTTP status code response.

  • Java API
server.request(by("foo")).response(status(200));
  • JSON
{
  "request" :
    {
      "text" : "foo"
    },
  "response" :
    {
      "status" : 200
    }
}

Version

@Since 0.7

By default, response HTTP version is supposed to request HTTP version, but you can set your own HTTP version:

  • Java API
server.response(version(HttpProtocolVersion.VERSION_1_0));
  • JSON
{
  "request":
    {
      "uri": "/version10"
    },
  "response":
    {
      "version": "HTTP/1.0"
    }
}

Header

@Since 0.7

We can also specify HTTP header in response.

  • Java API
server.request(by("foo")).response(header("content-type", "application/json"));
  • JSON
{
  "request" :
    {
      "text" : "foo"
    },
  "response" :
    {
      "headers" :
        {
          "content-type" : "application/json"
        }
    }
}

Proxy

Single URL

@Since 0.8

We can also response with the specified url, just like a proxy.

  • Java API
server.request(by("foo")).response(proxy("http://www.github.com"));
  • JSON
{
  "request" :
    {
      "text" : "foo"
    },
  "response" :
    {
      "proxy" : "http://www.github.com"
    }
}

Actually, proxy is more powerful than that. It can forward the whole request to the target url, including HTTP method, version, header, content etc.

Failover

@Since 0.7

Besides the basic functionality, proxy also support failover, which means if remote server is not available temporarily, the server will know recovery from local configuration.

  • Java API
server.request(by("foo")).response(proxy("http://www.github.com", failover("failover.json")));
  • JSON
{
  "request" :
    {
      "text" : "foo"
    },
  "response" :
    {
      "proxy" :
        {
          "url" : "http://localhost:12306/unknown",
          "failover" : "failover.json"
        }
    }
}

Proxy will save request/response pair into your failover file. If the proxy target is not reachable, proxy will failover from the file. This feature is very useful for development environment, especially for the case the integration server is not stable.

As the file suffix suggests, this failover file is actually a JSON file, which means we can read/edit it to return whatever we want.

Playback

@Since 0.9.1

Moco also supports playback which also save remote request and response into local file. The difference between failover and playback is that playback only accesses remote server when local request and response are not available.

  • Java API
server.request(by("foo")).response(proxy("http://www.github.com", playback("playback.json")));
  • JSON
{
  "request" :
    {
      "text" : "foo"
    },
  "response" :
    {
      "proxy" :
        {
          "url" : "http://localhost:12306/unknown",
          "playback" : "playback.json"
        }
    }
}

Customize Failover/Playback Status

@Since 1.0.0

You can customize what remote statuses means that remote server is not available.

  • Java API
server.request(by("foo")).response(proxy("http://www.github.com", failover("failover.json", 400, 500)));
  • JSON
{
    "request" :
    {
        "text": "foo"
    },
    "response" :
    {
        "proxy" :
        {
            "url" : "http://www.github.com",
            "failover" : {
                "file": "failover.json",
                "status": [404, 500]
            }
        }
    }
}

Batch URLs

@Since 0.9.1

If we want to proxy with a batch of URLs in the same context, proxy can also help us.

  • Java API
server.get(match(uri("/proxy/.*"))).response(proxy(from("/proxy").to("http://localhost:12306/target")));
  • JSON
{
    "request" :
    {
        "uri" : {
            "match" : "/proxy/.*"
        }
    },
    "response" :
    {
        "proxy" : {
            "from" : "/proxy",
            "to" : "http://localhost:12306/target"
        }
    }
}

Same with single url, you can also specify a failover.

  • Java API
server.request(match(uri("/proxy/.*")))
      .response(proxy("http://localhost:12306/unknown"), failover("failover.response")));
  • JSON
{
    "request" :
    {
        "uri" : {
            "match" : "/failover/.*"
        }
    },
    "response" :
    {
        "proxy" :
        {
            "from" : "/failover",
            "to" : "http://localhost:12306/unknown",
            "failover" : "failover.response"
        }
    }
}

and playback.

  • Java API
server.request(match(uri("/proxy/.*")))
      .response(proxy("http://localhost:12306/unknown"), playback("playback.response")));
  • JSON
{
    "request" :
    {
        "uri" : {
            "match" : "/failover/.*"
        }
    },
    "response" :
    {
        "proxy" :
        {
            "from" : "/failover",
            "to" : "http://localhost:12306/unknown",
            "playback" : "playback.response"
        }
    }
}

As you may find, we often set request match same context with response, so Moco gives us a shortcut to do that.

  • Java API
server.proxy(from("/proxy").to("http://localhost:12306/target"));
  • JSON
{
    "proxy" : {
        "from" : "/proxy",
        "to" : "http://localhost:12306/target"
    }
}

Same with failover

  • Java API

    server.proxy(from("/proxy").to("http://localhost:12306/unknown"), failover("failover.response"));
  • JSON

    {
      "proxy" :
      {
          "from" : "/failover",
          "to" : "http://localhost:12306/unknown",
          "failover" : "failover.response"
      }
    }

and playback

  • Java API

    server.proxy(from("/proxy").to("http://localhost:12306/unknown"), playback("playback.response"));
  • JSON

    {
      "proxy" :
      {
          "from" : "/failover",
          "to" : "http://localhost:12306/unknown",
          "playback" : "playback.response"
      }
    }

Redirect

@Since 0.7

Redirect is a common case for normal web development. We can simply redirect a request to different url.

  • Java API
server.get(by(uri("/redirect"))).redirectTo("http://www.github.com");
  • JSON
{
  "request" :
    {
      "uri" : "/redirect"
    },
  "redirectTo" : "http://www.github.com"
}

Cookie

@Since 0.7

Cookie can also be in the response.

  • Java API
server.response(cookie("loggedIn", "true"), status(302));
  • JSON
{
  "request" :
    {
      "uri" : "/cookie"
    },
  "response" :
    {
      "cookies" :
      {
        "login" : "true"
      }
    }
}

Cookie Attributes

Cookie attributes are sent in http response, which are used by browsers to determine when to delete a cookie, block a cookie or whether to send a cookie to the server.

Path

@Since 0.11.1

Path cookie attribute defines the scope of the cookie. You can add your own path cookie attribute to your response.

  • Java
server.response(cookie("loggedIn", "true", path("/")), status(302));
  • JSON
{
  "request" :
    {
      "uri" : "/cookie"
    },
  "response" :
    {
      "cookies" :
      {
        "login" : {
            "value" : "true",
            "path" : "/"
        }
      }
    }
}
Domain

@Since 0.11.1

Domain cookie attribute defines the scope of the cookie. You can add your own domain cookie attribute to your response.

  • Java
server.response(cookie("loggedIn", "true", domain("github.com")), status(302));
  • JSON
{
  "request" :
    {
      "uri" : "/cookie"
    },
  "response" :
    {
      "cookies" :
      {
        "login" : {
            "value" : "true",
            "domain" : "github.com"
        }
      }
    }
}
Secure

@Since 0.11.1

A secure cookie can only be transmitted over an encrypted connection. You can add your own secure cookie attribute to your response.

  • Java
server.response(cookie("loggedIn", "true", secure()), status(302));
  • JSON
{
  "request" :
    {
      "uri" : "/cookie"
    },
  "response" :
    {
      "cookies" :
      {
        "login" : {
            "value" : "true",
            "secure" : "true"
        }
      }
    }
}
HTTP Only

@Since 0.11.1

An http only cookie cannot be accessed by client-side APIs. You can add your own httpOnly cookie attribute to your response.

  • Java
server.response(cookie("loggedIn", "true", httpOnly()), status(302));
  • JSON
{
  "request" :
    {
      "uri" : "/cookie"
    },
  "response" :
    {
      "cookies" :
      {
        "login" : {
            "value" : "true",
            "httpOnly" : "true"
        }
      }
    }
}
Max Age

@Since 0.11.1

The Max-Age attribute can be used to set the cookie's expiration as an interval of seconds in the future, relative to the time the browser received the cookie. You can add your own maxAge cookie attribute to your response.

  • Java
server.response(cookie("loggedIn", "true", maxAge(1, TimeUnit.HOURS)), status(302))
  • JSON
{
  "request" :
    {
      "uri" : "/cookie"
    },
  "response" :
    {
      "cookies" :
      {
        "login" : {
            "value" : "true",
             "maxAge": {
                 "duration": 1,
                 "unit": "hour"
             }
        }
      }
    }
}
Same Site

@Since 1.5.0

The SameSite attribute can be set to control whether or not a cookie is sent with cross-site requests, providing some protection against cross-site request forgery attacks (CSRF).

Only "Strict", "Lax" and "None" can be set as sameSite value.

  • Java
server.response(cookie("loggedIn", "true", sameSite("Lax")), status(302))
  • JSON
{
  "request" :
    {
      "uri" : "/cookie"
    },
  "response" :
    {
      "cookies" :
      {
        "login" : {
            "value" : "true",
             "sameSite": "Lax"
        }
      }
    }
}

CORS

@Sinace 1.5.0

Cross-origin resource sharing (CORS) is a mechanism that allows restricted resources (e.g. fonts) on a web page to be requested from another domain outside the domain from which the resource originated. You can add your own cors to your response.

Default All CORS

You can add default all CORS to your response with cors operator without any arguments.

  • Java API
server.response(cors());
  • JSON API
{
  "response" :
    {
      "cors" : true
    }
}

CORS with allowOrigin/Access-Control-Allow-Origin

You can allow CORS with specific origin with cors operator with allowOrigin argument, which provides Access-Control-Allow-Origin header.

  • Java API
server.response(cors(allowOrigin("https://www.github.com/")));
  • JSON API
{
  "response" :
    {
      "cors" :
        {
          "allowOrigin" : "https://www.github.com/"
        }
    }
}

If you allow any origin with * in origin.

  • Java API
server.response(cors(origin("*")));
  • JSON
{
  "response" :
    {
      "cors" :
        {
          "allowOrigin" : "*"
        }
    }
}

In JSON API, you can also use Access-Control-Allow-Origin directly.

  • JSON
{
  "response" :
    {
      "cors" :
        {
          "Access-Control-Allow-Origin" : "https://www.github.com"
        }
    }
}

CORS with allowMethods/Access-Control-Allow-Methods

You can allow CORS with specific methods with cors operator with allowMethods argument, which provides Access-Control-Allow-Methods header.

  • Java API
server.response(cors(allowMethods("GET", "PUT")));
server.response(cors(allowMethods(HttpMethod.GET, HttpMethod.PUT)));
  • JSON
{
  "response" :
    {
      "cors" :
        {
          "allowMethods" : ["GET", "PUT"],
        }
    }
}

In JSON API, you can also use Access-Control-Allow-Methods directly.

  • JSON
{
  "response" :
    {
      "cors" :
        {
          "Access-Control-Allow-Methods" : ["GET", "PUT"]
        }
    }
}

CORS with allowHeaders/Access-Control-Allow-Headers

You can allow CORS with specific headers with cors operator with allowHeaders argument, which provides Access-Control-Allow-Headers header.

  • Java API
server.response(cors(allowHeaders("X-Header", "Y-Header")));
  • JSON
{
  "response" :
    {
      "cors" :
        {
          "allowHeaders" : ["X-Header", "Y-Header"]
        }
    }
}

In JSON API, you can also use Access-Control-Allow-Methods directly.

{
  "response" :
    {
      "cors" :
        {
          "Access-Control-Allow-Headers" : ["X-Header", "Y-Header"]
        }
    }
}

CORS with maxAge/Access-Control-Max-Age

You can allow CORS with specific max age with cors operator with maxAge argument, which provides Access-Control-Max-Age header.

  • Java API
server.response(cors(maxAge(1728000, TimeUnit.SECONDS)));
  • JSON
{
  "response" :
    {
      "cors" :
        {
          "maxAge": {
            "duration": 1728000,
            "unit": "second"
          }
        }
    }
}

In JSON API, you can also use Access-Control-Max-Age directly.

{
  "response" :
    {
      "cors" :
        {
          "Access-Control-Max-Age" : {
            "duration": 1728000,
            "unit": "second"
          }
        }
    }
}

CORS with allowCredentials/Access-Control-Allow-Credentials

You can allow CORS with specific credentials with cors operator with allowCredentials argument, which provides Access-Control-Allow-Credentials header.

  • Java API
server.response(cors(allowCredentials(true)));
  • JSON
{
  "response" :
    {
      "cors" :
        {
          "allowCredentials" : true
        }
    }
}

In JSON API, you can also use Access-Control-Allow-Credentials directly.

{
  "response" :
    {
      "cors" :
        {
          "Access-Control-Allow-Credentials" : true
        }
    }
}

CORS with exposeHeaders/Access-Control-Expose-Headers

You can allow CORS with specific expose headers with cors operator with exposeHeaders argument, which provides Access-Control-Expose-Headers header.

  • Java API
server.response(cors(exposeHeaders("X-Header", "Y-Header")));
  • JSON
{
  "response" :
    {
      "cors" :
        {
          "exposeHeaders" : ["X-Header", "Y-Header"]
        }
    }
}

In JSON API, you can also use Access-Control-Expose-Headers directly.

{
  "response" :
    {
      "cors" :
        {
          "Access-Control-Expose-Headers" : ["X-Header", "Y-Header"]
        }
    }
}

Attachment

@Since 0.10.0

Attachment is often used in web development. You can setup an attachment in Moco as following. As you will see, you'd better set a filename for client to receive.

  • Java API
server.get(by(uri("/"))).response(attachment("foo.txt", file("foo.response")));
  • JSON
{
  "request": {
    "uri": "/file_attachment"
  },
  "response": {
    "attachment": {
        "filename": "foo.txt",
        "file": "foo.response"
    }
  }
}

Latency

@Since 0.7

Sometimes, we need a latency to simulate slow server side operation.

@Since 0.10.1

It's easy to setup latency with time unit.

  • Java API
server.response(latency(1, TimeUnit.SECONDS));
  • JSON
{
  "request" :
    {
      "text" : "foo"
    },
  "response" :
    {
      "latency":
        {
          "duration": 1,
          "unit": "second"
        }
    }
}

The original API without time unit introduced in 0.7 has been deprecated.

Sequence

@Since 0.7

Sometimes, we want to simulate a real-world operation which change server side resource. For example:

  • First time you request a resource and "foo" is returned
  • We update this resource
  • Again request the same URL, updated content, e.g. "bar" is expected.

We can do that by

server.request(by(uri("/seq"))).response(seq("foo", "bar", "blah"));

The other response settings are able to be set as well.

server.request(by(uri("/seq"))).response(seq(status(302), status(302), status(200)));

@Since 0.12.0

{
    "request" : {
      "uri" : "/seq"
    },
    "response": {
      "seq": [
          {
            "text" : "foo"
          },
          {
            "text" : "bar"
          },
          {
            "text" : "blah"
          }
      ]
    }
}

The other response settings are able to be set for json as well.

{
    "request" : {
      "uri" : "/seq"
    },
    "response": {
      "seq": [
          {
            "status" : "302"
          },
          {
            "status" : "302"
          },
          {
            "status" : "200"
          }
      ]
    }
}

Cycle

@Since 1.0.0

Cycle is similar to seq, but it will return response as cycle. An example is as following:

server.request(by(uri("/cycle"))).response(cycle("foo", "bar", "blah"));

The response will returned as cycle:

  • foo
  • bar
  • blah
  • foo
  • bar
  • blah
  • ...

The other response settings are able to be set as well.

server.request(by(uri("/cycle"))).response(cycle(status(302), status(302), status(200)));

@Since 0.12.0

{
    "request" : {
      "uri" : "/cycle"
    },
    "response": {
      "cycle": [
          {
            "text" : "foo"
          },
          {
            "text" : "bar"
          },
          {
            "text" : "blah"
          }
      ]
    }
}

The other response settings are able to be set for json as well.

{
    "request" : {
      "uri" : "/cycle"
    },
    "response": {
      "cycle": [
          {
            "status" : "302"
          },
          {
            "status" : "302"
          },
          {
            "status" : "200"
          }
      ]
    }
}

JSON Response

If the response is JSON, we don't need to write JSON text with escape character in code.

@Since 0.10.2

You can give a POJO to Java API, it will be converted JSON text. Hint, this api will setup Content-Type header as well.

server.request(by(uri("/json"))).response(toJson(pojo));

@Since 0.12.0 toJson will be removed from 0.12.0, use json instead.

server.request(by(uri("/json"))).response(json(pojo));

Note that this functionality is implemented in Jackson, please make sure your POJO is written in Jackson acceptable format.

@Since 0.9.2

For JSON API, just give json object directly

{
    "request": 
      {
        "uri": "/json"
      },
    "response": 
      {
        "json": 
          {
            "foo" : "bar"
          }
      }
}

@Since 1.2.0

For API user, if you want to return create dynamic JSON based on the request, you can use lambda to do this.

  • With request
server.request(by(uri("/json"))).response(json((request) -> new Pojo()));

Mount

@Since 0.7

Moco allows us to mount a directory to uri.

  • Java API
server.mount(dir, to("/uri"));
  • JSON
{
  "mount" :
    {
      "dir" : "dir",
      "uri" : "/uri"
    }
}

Glob is acceptable to filter specified files, e.g we can include by

  • Java API
server.mount(dir, to("/uri"), include("*.txt"));
  • JSON
{
  "mount" :
    {
      "dir" : "dir",
      "uri" : "/uri",
      "includes" :
        [
          "*.txt"
        ]
    }
}

or exclude by

  • Java API
server.mount(dir, to("/uri"), exclude("*.txt"));
  • JSON
{
  "mount" :
    {
      "dir" : "dir",
      "uri" : "/uri",
      "excludes" :
        [
          "*.txt"
        ]
    }
}

even compose them by

  • Java API
server.mount(dir, to("/uri"), include("a.txt"), exclude("b.txt"), include("c.txt"));
  • JSON
{
  "mount" :
    {
      "dir" : "dir",
      "uri" : "/uri",
      "includes" :
        [
          "a.txt",
          "c.txt"
        ],
      "excludes" :
        [
          "b.txt"
        ]
    }
}

@Since 0.10.1 You can also specify some response information like normal response, e.g.

  • JSON
{
  "mount" :
    {
      "dir" : "dir",
      "uri" : "/uri",
      "headers" : {
        "Content-Type" : "text/plain"
      }
    }
}

Template

Sometimes, we need to customize our response based on something, e.g. response should have same header with request.

The goal can be reached by template:

Request

You can get request information with req in template.

Version

@Since 0.8

With req.version, request version can be retrieved in template.

The following example will return response version as content.

  • Java
server.request(by(uri("/template"))).response(template("${req.version}"));
  • JSON
{
    "request": {
        "uri": "/template"
    },
    "response": {
        "text": {
            "template": "${req.version}"
        }
    }
}

Method

@Since 0.8

Request method is identified by req.method.

  • Java
server.request(by(uri("/template"))).response(template("${req.method}"));
  • JSON
{
    "request": {
        "uri": "/template"
    },
    "response": {
        "text": {
            "template": "${req.method}"
        }
    }
}

Content

@Since 0.8

All request content can be used in template with req.content

  • Java
server.request(by(uri("/template"))).response(template("${req.content}"));
  • JSON
{
    "request": {
        "uri": "/template"
    },
    "response": {
        "text": {
            "template": "${req.content}"
        }
    }
}

Header

@Since 0.8

Header is another important element in template and we can use req.headers for headers.

  • Java
server.request(by(uri("/template"))).response(template("${req.headers['foo']}"));
  • JSON
{
    "request": {
        "uri": "/template"
    },
    "response": {
        "text": {
            "template": "${req.headers['foo']}"
        }
    }
}

Query

@Since 0.8

req.queries helps us to extract request query.

  • Java
server.request(by(uri("/template"))).response(template("${req.queries['foo']}"));
  • JSON
{
    "request": {
        "uri": "/template"
    },
    "response": {
        "text": {
            "template": "${req.queries['foo']}"
        }
    }
}

Form

@Since 0.9.1

req.forms can extract form value from request.

  • Java
server.request(by(uri("/template"))).response(template("${req.forms['foo']}"));
  • JSON
{
    "request": {
        "uri": "/template"
    },
    "response": {
        "text": {
            "template": "${req.forms['foo']}"
        }
    }
}

Cookie

@Since 0.9.1

Cookie from request can extracted by req.cookies.

  • Java
server.request(by(uri("/template"))).response(template("${req.cookies['foo']}"));
  • JSON
{
    "request": {
        "uri": "/template"
    },
    "response": {
        "text": {
            "template": "${req.cookies['foo']}"
        }
    }
}

JSON

@Since 1.0.0

If your request is a JSON request, you can use req.json to visit your json object.

Note that make sure your request is a JSON request, otherwise an exception will be thrown.

  • Java
server.request(by(uri("/template"))).response(template("${req.json.foo}"));
  • JSON
{
    "request": {
        "uri": "/template"
    },
    "response": {
        "text": {
            "template": "${req.json.foo}"
        }
    }
}

XML

@Since 1.4.0

If your request is an XML request, you can use req.xml to visit your xml object.

Note that make sure your request is an XML request, otherwise an exception will be thrown.

  • Java
server.request(by(uri("/template"))).response(template("${req.xml.foo}"));
  • JSON
{
    "request": {
        "uri": "/template"
    },
    "response": {
        "text": {
            "template": "${req.xml.foo}"
        }
    }
}

Client

Address

@Since 1.4.0

req.client.address can be used in template to return client IP address.

server.request(by(uri("/template"))).response(template("${req.client.address}"));
{
    "request": {
        "uri": "/template"
    },
    "response": {
        "text": {
            "template": "${req.client.address}"
        }
    }
}
Port

@Since 1.5.0

req.client.port can be used in template to return client port.

server.request(by(uri("/template"))).response(template("${req.client.port}"));
{
    "request": {
        "uri": "/template"
    },
    "response": {
        "text": {
            "template": "${req.client.port}"
        }
    }
}

Path

@Since 1.4.0

req.path can work with path API to extract path parameter as template variable.

server.request(path(uri("/path/{foo}"))).response(template("${req.path.foo}"));
{
  "request": {
    "uri": {
      "path": "/path/{foo}"
    }
  },
  "response": {
    "text": {
      "template": "${req.path.foo}"
    }
  }
}

Custom Variable

@Since 0.9.1

You can provide your own variables in your template.

  • Java
server.request(by(uri("/template"))).response(template("${foo}", "foo", "bar"));
  • JSON
{
    "request": {
        "uri": "/template"
    },
    "response": {
        "text": {
            "template": {
                "with" : "${foo}",
                "vars" : {
                    "foo" : "bar"
                }
            }
        }
    }
}

@Since 0.10.0

You can also use extractor to extract information from request.

  • Java
server.request(by(uri("/template"))).response(template("${foo}", "foo", jsonPath("$.book[*].price")));
  • JSON
{
    "request": {
        "uri": "/template"
    },
    "response": {
        "text": {
            "template": {
                "with" : "${foo}",
                "vars" : {
                    "foo" : {
                      "json_path": "$.book[*].price"
                    }
                }
            }
        }
    }
}

Other extractors, e.g. xpath also work here.

Template Function

now

@Since 1.0.0

Current time can retrieved by 'now' function and a date format string should be passed as argument.

  • Java
server.request(by(uri("/template"))).response(template("${now('yyyy-MM-dd')}"));
  • JSON
{
    "request": {
        "uri": "/template"
    },
    "response": {
        "text": {
            "template": "${now(\"yyyy-MM-dd\")}"
        }
    }
}

random

@Since 1.0.0

random will generate a random number. If you didn't pass any argument, the generated random will be between 0 and 1.

  • Java
server.request(by(uri("/template"))).response(template("${random()}"));
  • JSON
{
    "request": {
        "uri": "/template"
    },
    "response": {
        "text": {
            "template": "${random()}"
        }
    }
}

The first argument is random number range which means the generated number will be between 0 and range.

  • Java
server.request(by(uri("/template"))).response(template("${random(100)}"));
  • JSON
{
    "request": {
        "uri": "/template"
    },
    "response": {
        "text": {
            "template": "${random(100)}"
        }
    }
}

@Since 1.3.0

If you want to limit your random number in a range. You can give two number as a start and an end.

  • Java
server.request(by(uri("/template"))).response(template("${random(99, 100)}"));
  • JSON
{
    "request": {
        "uri": "/template"
    },
    "response": {
        "text": {
            "template": "${random(99, 100)}"
        }
    }
}

The last argument could be a number format.

  • Java
server.request(by(uri("/template"))).response(template("${random(99, 100, '###.###')}"));
  • JSON
{
    "request": {
        "uri": "/template"
    },
    "response": {
        "text": {
            "template": "${random(99, 100, \"###.###\")}"
        }
    }
}

You can also use number format directly without range. By default, range is from 0 to 1.

  • Java
server.request(by(uri("/template"))).response(template("${random('###.###')}"));
  • JSON
{
    "request": {
        "uri": "/template"
    },
    "response": {
        "text": {
            "template": "${random(\"###.###\")}"
        }
    }
}

Redirect

@Since 0.10.2

Redirect can also be set as template.

  • Java
server.request(by(uri("/redirectTemplate"))).redirectTo(template("${var}", "var", ""https://github.com"));
  • Json
{
      "request" :
      {
          "uri" : "/redirect-with-template"
      },

      "redirectTo" : {
          "template" : {
              "with" : "${url}",
              "vars" : {
                  "url" : "https://github.com"
              }
          }
      }
  }

File Name Template

@Since 0.10.1

Template can also be used in file name, thus response can be different based on different request.

  • Java
server.response(file(template("${req.headers['foo'].txt")));
  • JSON
[
  {
    "response": {
      "file": {
        "name": {
          "template": "${req.headers['foo'].txt"}"
        }
      }
    }
  }
]

Proxy

@Since 0.11.1

You can use template in proxy API, so that you can dynamically decide which URL you will forward the request to.

  • Java
server.request(by(uri("/proxy"))).response(proxy(template("http://localhost:12306/${req.queries['foo']}")))
  • JSON
{
    "request" : {
        "uri" : "/proxy"
    },
    "response" : {
        "proxy" : {
            "url" : {
                "template": "http://localhost:12306/${req.queries['foo']}"
            }
        }
    }
}

Template for Event Action

Template also can ben applied to event action. Check out Event for more details about event.

  • Java
server.request(by(uri("/event"))).response("event").on(complete(post("http://localhost:12306/target"), template("${target}", of("target", var("target"))))));
  • JSON
{
  "request": {
    "uri": "/event"
  },
  "response": {
    "text": "event"
  },
  "on": {
    "complete": {
      "post": {
        "url": "http://localhost:12306/target",
        "content": {
          "template": {
            "with": "${target}",
            "vars": {
              "target" : "target"
            }
          }
        }
      }
    }
  }
}

Record and Replay

@Since 1.1.0

More powerful dynamic features are required even if you can implement some with a template. For instance, you may want to change one URL to return a different response. Record and replay will help.

In the following, /record will be used to record request and /replay will return the recorded request content. You can also configure record and replay for more capabilities.

In this case, group will be used to distinguish different record sources.

server.request(by(uri("/record"))).response(record(group("foo")));
server.request(by(uri("/replay"))).response(replay(group("foo")));

Group

group help you distinguish different record source, which means you can have same configuration in different group. If no group is provided, default group will be applied.

  • Java
server.request(by(uri("/record"))).response(record(group("foo")));
server.request(by(uri("/replay"))).response(replay(group("foo")));
  • JSON

The default parameter for record and replay is group.

[
  {
    "request" : {
      "uri" : "/record"
    },
    "response" : {
      "record" : "foo"
    }
  },
  {
    "request" : {
      "uri" : "/replay"
    },
    "response" : {
      "record" : "foo"
    }
  }
]

You can also specify group name explicitly.

[
  {
    "request" : {
      "uri" : "/record"
    },
    "response" : {
      "record" : {
        "group": "foo"
      }
    }
  },
  {
    "request" : {
      "uri" : "/replay"
    },
    "response" : {
      "record" : {
        "group": "foo"
      }
    }
  }
]

Identifier

In the same group, you can use identifier to distinguish different requst. You can extract identifier from request with template syntax.

  • Java
server.request(by(uri("/record"))).response(record(identifier("${req.queries['type']}")));
server.request(by(uri("/replay"))).response(replay(identifier("${req.queries['type']}")));
  • JSON
[
  {
    "request" : {
      "uri" : "/record"
    },
    "response" : {
      "record" : {
        "identifier": {
          "template": "${req.queries['type']}"
        }
      }
    }
  },
  {
    "request" : {
      "uri" : "/replay"
    },
    "response" : {
      "record" : {
        "identifier": {
          "template": "${req.queries['type']}"
        }
      }
    }
  }
]

In the above case, if you record your request with

  • URI /record?type=foo and content is foo
  • URI /record?type=bar and content is bar,

When you access URI /replay?type=foo, foo will be returned and access URI /replay?type=bar, bar will be returned.

Modifier

The recorded content will be returned by default. But sometimes you hope return different content.

In the following case, with modifier, type in request parameter will be returned as replay content. As you expect, modifier only apply in replay. Template syntax will apply here.

  • Java
server.request(by(uri("/record"))).response(record(group("foo")));
server.request(by(uri("/replay"))).response(replay(group("foo"),    
                                                   modifier("${req.queries['type']}")));
  • JSON
[
  {
    "request" : {
      "uri" : "/record"
    },
    "response" : {
      "record" : {
        "group": "foo",
      }
    }
  },
  {
    "request" : {
      "uri" : "/replay"
    },
    "response" : {
      "record" : {
        "group": "foo",
        "modifier": "${req.queries['type']}"
      }
    }
  }
]

By default, only response content will be set with modifier. If you want more, you can setup the response with other response configuration.

  • Java
server.request(by(uri("/record"))).response(record(group("foo")));
server.request(by(uri("/replay"))).response(replay(group("foo"),    
    modifier(template("${req.content}"),
             header("X-REPLAY", template("${req.queries['type']}"))
    )
));
  • JSON
[
  {
    "request" : {
      "uri" : "/record"
    },
    "response" : {
      "record" : {
        "group": "foo",
      }
    }
  },
  {
    "request" : {
      "uri" : "/replay"
    },
    "response" : {
      "record" : {
        "group": "foo",
        "modifier": {
          "text": {
            "template": "${req.content}"
          },
          "header": {
            "X-REPLAY": {
              "template": "${req.queries['type']}"
            }
          }
        }
      }
    }
  }
]

Tape

If you want to reuse recorded request after restart, tape will help you persist the recorded request just like a tape.

  • Java
server.request(by(uri("/record"))).response(record(group("foo"), tape("/path/to/tape")));
server.request(by(uri("/foo-replay"))).response(replay(record(group("foo"), 
                                                              tape("/path/to/tape")));
  • JSON
[
  {
    "request" : {
      "uri" : "/record"
    },
    "response" : {
      "record" : {
        "group": "foo",
        "tape": "/path/to/tape"
      }
    }
  },
  {
    "request" : {
      "uri" : "/replay"
    },
    "response" : {
      "record" : {
        "group": "foo",
        "tape": "/path/to/tape"
      }
    }
  }
]

Event

You may need to request another site when you receive a request, e.g. OAuth. Event could be your helper at that time.

Complete

@Since 0.9

Complete event will be fired after your request has been handled completely.

Get Request

You can get from an URL.

  • Java
server.request(by(uri("/event"))).response("event").on(complete(get("http://another_site")));
  • JSON
{
    "request": {
        "uri" : "/event"
    },
    "response": {
        "text": "event"
    },
    "on": {
        "complete": {
            "get" : {
                "url" : "http://another_site"
            }
        }
    }
}

@Since 1.0.0

And also get with headers.

  • Java
server.request(by(uri("/event"))).response("event").on(complete(get("http://another_site", header("foo", "bar"))));
  • JSON
{
    "request": {
        "uri" : "/event"
    },
    "response": {
        "text": "event"
    },
    "on": {
        "complete": {
            "get" : {
                "url" : "http://another_site",
                "headers": {
                    "foo": "bar"
                }
            }
        }
    }
}

Post Request

You can post some content as well.

  • Java
server.request(by(uri("/event"))).response("event").on(complete(post("http://another_site", "content")));
  • JSON
{
    "request": {
        "uri" : "/event"
    },
    "response": {
        "text": "event"
    },
    "on": {
        "complete": {
            "post" : {
                "url" : "http://another_site",
                "content": "content"
            }
        }
    }
}

@Since 0.12.0

If your post content is JSON, you can use json in your configuration directly.

{
    "request": {
        "uri" : "/event"
    },
    "response": {
        "text": "event"
    },
    "on": {
        "complete": {
            "post" : {
                "url" : "http://another_site",
                "json": {
                    "foo" : "bar"
                }
            }
        }
    }
}

@Since 1.0.0

And also post with headers.

  • Java
server.request(by(uri("/event"))).response("event").on(complete(post("http://another_site", "content", header("foo", "bar"))));
  • JSON
{
    "request": {
        "uri" : "/event"
    },
    "response": {
        "text": "event"
    },
    "on": {
        "complete": {
            "post" : {
                "url" : "http://another_site",
                "content": "content",
                "headers": {
                    "foo": "bar"
                }
            }
        }
    }
}

Let me know if you need more methods.

Asynchronous

@Since 0.9

Synchronized request is used by default, which means response won't be returned to client until event handler is finished.

If it is not your expected behavior, you can changed with async API which will fire event asynchronously.

  • Java
server.request(by(uri("/event"))).response("event").on(complete(async(post("http://another_site", "content"))));
  • JSON
{
    "request": {
        "uri" : "/event"
    },
    "response": {
        "text": "event"
    },
    "on": {
        "complete": {
            "async" : "true",
            "post" : {
                "url" : "http://another_site",
                "content": "content"
            }
        }
    }
}

You can specify latency for this asynchronous request as well to wait a while.

  • Java
server.request(by(uri("/event"))).response("event").on(complete(async(post("http://another_site", "content"), latency(1000))));
  • JSON
{
    "request": {
        "uri" : "/event"
    },
    "response": {
        "text": "event"
    },
    "on": {
        "complete": {
            "async" : "true",
            "latency" : 1000,
            "post" : {
                "url" : "http://another_site",
                "content": "content"
            }
        }
    }
}

Verify

@Since 0.9

Someone may want to verify what kind of request has been sent to server in testing framework.

You can verify request like this:

RequestHit hit = requestHit();
final HttpServer server = httpServer(12306, hit);
server.get(by(uri("/foo"))).response("bar");

running(server, () -> {
    assertThat(helper.get(remoteUrl("/foo")), is("bar"));
});

hit.verify(by(uri("/foo"))), times(1));

You can also verify unexpected request like this:

hit.verify(unexpected(), never());

Many verification can be used:

  • never: none of this kind of request has been sent.
  • once: only once this kind of request has been sent.
  • time: how many times this kind of request has been sent.
  • atLeast: at least how many time this kind of request has been sent.
  • atMost: at most how many time this kind of request has been sent.
  • between: the times this kind of request has been sent should be between min and max times.

Miscellaneous

Port

@Since 0.9

If you specify a port for your stub server, it means the port must be available when you start server. This is not case sometimes.

Moco provides you another way to start your server: specify no port, and it will look up an available port. The port can be got by port() method. The example is as follow:

final HttpServer server = httpServer();
server.response("foo");

running(server, () -> {
    Content content = Request.Get("http://localhost:" + server.port()).execute().returnContent();
    assertThat(content.asString(), is("foo"));
});

The port will be returned only when server is started, otherwise the exception will be thrown.

For standalone server, if you need this behaviour, simply don't give port argument.

java -jar moco-runner-<version>-standalone.jar http -c foo.json

The port information will shown on screen.

Log

@Since 0.9.1

If you want to know more about how your Moco server running, log will be your helper.

final HttpServer server = httpServer(log());

The Moco server will log all your requests and responses in your console.

It you want to keep log, you can use log interface as following:

final HttpServer server = httpServer(log("path/to/log.log"));

@Since 0.10.1

Log content may contain some non UTF-8 character, charset could be specified in log API:

final HttpServer server = httpServer(log("path/to/log.log", Charset.forName("GBK")));

The log will be saved in your log file.

Log with verifier

Log will help you for some legacy system to know what detailed request/response looks like. You also need to do some verification work. Here is the case.

RequestHit hit = requestHit();
final HttpServer server = httpServer(port(), hit, log());