# Features
# Sending HTTP Request
Possible HTTP request methods are GET
, POST
, PUT
, DELETE
, OPTIONS
, HEAD
# GET request
request get-example {
method = "GET"
url = "http://httpbin.org/get"
}
Execution
$ restbeast r get-example
HTTP/1.1 200 OK
{
"args": {},
"headers": {
"Accept-Encoding": "gzip",
"Host": "httpbin.org",
"User-Agent": "RestBeast/0.13.0",
"X-Amzn-Trace-Id": "Root=1-600b6b8f-639ca132489f3d353fcd8760"
},
"origin": "213.127.110.240",
"url": "http://httpbin.org/get"
}
# POST request
request post-example {
method = "POST"
url = "http://httpbin.org/post"
body = {
key = "value"
key-2 = "value-2"
}
}
$ restbeast r post-example
HTTP/1.1 200 OK
{
"args": {},
"data": "{\n \"key\": \"value\",\n \"key-2\": \"value-2\"\n}",
"files": {},
"form": {},
"headers": {
"Accept-Encoding": "gzip",
"Content-Length": "42",
"Host": "httpbin.org",
"User-Agent": "RestBeast/0.13.0",
"X-Amzn-Trace-Id": "Root=1-600b6c10-1f2c17c43cd1077112ce8302"
},
"json": {
"key": "value",
"key-2": "value-2"
},
"origin": "213.127.110.240",
"url": "http://httpbin.org/post"
}
# Content Type Handling
Special cases:
application/json
body
will be parsed into a json stringapplication/x-www-form-urlencoded
body
will be parsed as url-encoded key=value stringmultipart/form-data
body
will be half parsed for attributes, anyreadfile
operations will be added as boundary.files uploads
Request body will be the contents of the file, content-type header will be set with file's mime type in case of left blank
# Built-in Functions
A variety of functions are available.
See built-in go-cty functions and gofakeit functions
# External (Custom) Functions
It's possible to define external programs or scripts as functions.
Possible argument types are string
, list
, map
, number
.
Given contents of test-function.js
as follows.
process.stdout.write(process.argv[2].toUpperCase());
external-function testFunction {
interpreter = "node"
script = "test-function.js"
args = ["string"]
}
request function-example {
method = "GET"
url = "localhost/${testFunction("hello")}"
}
# Multi Environment Configurations
Environment variables and related secrets can be changed with just a simple env flag.
env prod {
variables = {
url = "http://httpbin.org"
apiKey = "oh-my-secret"
}
}
env local {
default = true
variables = {
url = "http://localhost"
apiKey = "not-so-important-secret"
}
}
request env-example {
method = "GET"
url = "${env.url}/get"
headers = {
"x-api-key" = env.apiKey
}
}
Execute with -e
, useful for testing against various environments or testing in CI pipelines
$ restbeast request env-example --env prod
# Secrets
env local {
default = true
secrets from_shell_env {
type = "env-var"
paths = {
apikey = "APIKEY"
}
}
variables = {
url = "http://localhost:8080"
apiKey = secret.from_shell_env.apikey
}
}
request secret-example {
method = "GET"
url = "${env.url}/get"
headers = {
"x-api-key" = env.apiKey
}
}
$ restbeast_var_APIKEY="very-secret-key" restbeast request secret-example --env local
# Secrets from environment variables
env test {
secrets env {
type = "env-var"
paths = {
val1 = "VAL1"
val2 = "VAL2"
}
}
}
Prefix environment variables with restbeast_var_
$ restbeast_var_VAL1=secret1 restbeast_var_VAL2=secret2 restbeast r xxx --env test
# Generating Random Data
Leverage https://github.com/brianvoe/gofakeit
library in your requests.
An example with randomized user data
request random-example {
method = "POST"
url = "https://httpbin.org/post"
headers = {
"content-type" = "application/json"
}
body = {
firstName = gofakeitFirstName()
lastName = gofakeitLastName()
}
}
# Chaining requests
# Without pre-configuration
Dependencies will be resolved through usage of request.*
phrases.
When patch-example
request executed, it will do a post-example
request first and use its response as a dependency in patch-example
request.
request post-example {
method = "POST"
url = "https://httpbin.org/post"
headers = {
"content-type" = "application/json"
}
body = {
firstName = gofakeitFirstName()
lastName = gofakeitLastName()
}
}
request patch-example {
method = "PATCH"
url = "https://httpbin.org/patch"
headers = {
"content-type" = "application/json"
"X-Amzn-Trace-Id": request.post-example.body.headers.X-Amzn-Trace-Id
}
body = {
firstName = "Mr. ${upper(request.post-example.body.json.firstName)}"
lastName = "Mr. ${upper(request.post-example.body.json.lastName)}"
contentLengthOfFirstResponse = request.post-example.headers["content-length"][0]
}
}
$ restbeast request patch-example | jq
Example response will contain the first, and the second X-Amzn-Trace-Id
also
it will include upper-cased values of firstName and lastName generated in post-example
request
Output
{
"args": {},
"data": "{\n \"firstName\": \"Mr. JUNIOR\",\n \"lastName\": \"Mr. RUSSEL\"\n}",
"files": {},
"form": {},
"headers": {
"Accept-Encoding": "gzip",
"Content-Length": "59",
"Content-Type": "application/json",
"Host": "httpbin.org",
"User-Agent": "RestBeast-v0.4.0",
"X-Amzn-Trace-Id": "Self=1-5f15a0f8-cd3f68948e10488e520dcb5a;Root=1-5f15a0f8-26cd99544146b6540841f272"
},
"json": {
"firstName": "Mr. JUNIOR",
"lastName": "Mr. RUSSEL"
},
"origin": "213.127.104.191",
"url": "https://httpbin.org/patch"
}
# With depends_on parameter
The following example doesn't actually require explicit dependency declaration, however there might be some cases defining dependency order would be useful.
request post-example-1 {
method = "POST"
url = "https://httpbin.org/post"
headers = {
"content-type" = "application/json"
}
body = {
firstName = gofakeitFirstName()
lastName = gofakeitLastName()
}
}
request post-example-2 {
method = "POST"
url = "https://httpbin.org/post"
headers = {
"content-type" = "application/json"
}
body = {
firstName = request.post-example-1.body.json.firstName
lastName = gofakeitLastName()
}
}
request patch-example {
method = "PATCH"
url = "https://httpbin.org/patch"
depends_on = ["request.post-example-1", "request.post-example-2"]
headers = {
"content-type" = "application/json"
"X-Amzn-Trace-Id": request.post-example.body.headers.X-Amzn-Trace-Id
}
body = {
firstName = "Mr. ${upper(request.post-example-1.body.json.firstName)}"
lastName = "Mr. ${upper(request.post-example-2.body.json.lastName)}"
contentLengthOfFirstResponse = request.post-example.headers["content-length"][0]
}
}
Should result as
$ restbeast r patch-example --detailed-timing
HTTP/2.0 200 OK
│
├──PATCH https://httpbin.org/patch
│ DNS Resolve Time: 0 ms
│ Connection Time: 0 ms
│ First Byte Time: 204 ms
│ Total Time: 204 ms
│ Bytes Received: 687 B
│ │
│ ├──POST https://httpbin.org/post
│ │ DNS Resolve Time: 19 ms
│ │ Connection Time: 187 ms
│ │ TLS Handshake Time: 596 ms
│ │ First Byte Time: 803 ms
│ │ Total Time: 803 ms
│ │ Bytes Received: 543 B
│ │
│ ├──POST https://httpbin.org/post
│ │ DNS Resolve Time: 0 ms
│ │ Connection Time: 0 ms
│ │ First Byte Time: 203 ms
│ │ Total Time: 203 ms
│ │ Bytes Received: 539 B
│ │
│ ├──POST https://httpbin.org/post
│ │ DNS Resolve Time: 0 ms
│ │ Connection Time: 0 ms
│ │ First Byte Time: 205 ms
│ │ Total Time: 205 ms
│ │ Bytes Received: 535 B
{
"args": {},
"data": "{\n \"contentLengthOfFirstResponse\": \"535\",\n \"firstName\": \"Mr. ULISES\",\n \"lastName\": \"Mr. BRAUN\"\n}",
"files": {},
"form": {},
"headers": {
"Accept-Encoding": "gzip",
"Content-Length": "99",
"Content-Type": "application/json",
"Cookie": "",
"Host": "httpbin.org",
"User-Agent": "RestBeast/0.13.0",
"X-Amzn-Trace-Id": "Self=1-60232377-4344fdb752fb80b01d766132;Root=1-60232377-3409ee3a3659b2ef438f84b6"
},
"json": {
"contentLengthOfFirstResponse": "535",
"firstName": "Mr. ULISES",
"lastName": "Mr. BRAUN"
},
"origin": "213.127.105.230",
"url": "https://httpbin.org/patch"
}
# Attack request (Load testing)
Keep targeted server busy. This command will execute given request c
times in given p
period.
Request count has to be equal or higher than 1 request per second.
$ restbeast ar test-request-name -c 60 -p 60s
Example output
Status 200 response: %78 (47)
Status 400 response: %15 (9)
Status 500 response: %6 (4)
95 Percentile: 1.091473938s
99 Percentile: 1.100081803s
AverageTime: 585.411933ms
# Fixing resbeast version
It's possible to fix your restbeast configuration to a specific version. See here for extra comparison options.
version = "~0.9"
# Authorization
It's possible to handle basic or bearer authorization through auth
block.
Basic auth example
request example {
auth {
basic {
username = "a-username"
password = request.some-other-request.body.password
}
}
}
Bearer auth example
request example {
auth {
bearer {
token = request.sign-in.body.jwt
}
}
}
# Testing and Assertions
request get-example {
method = "GET"
url = "http://httpbin.org/get"
}
test get-example {
valid-origin = assertIpv4(request.get-example.body.origin)
valid-host = assertEqual(request.get-example.body.headers.Host, "httpbin.org")
}
Running this example with test
command will provide the output below. With exit code as 0
. Failed tests will result exit code 1
and fail descriptions.
$ restbeast test get-example
PASS: valid-host
PASS: valid-origin
2 passes, 0 failures, 2 total.
Please see assertions.
Just run the test command without any arguments to run all tests at once
$ restbeast test
# Sending Cookies
It's possible to send cookies as a header or via cookies
block. Both can be used simultaneously.
request cookies-example {
method = "GET"
url = "https://httpbin.org/cookies"
headers = {
cookie = "name=value; another-name=another-value"
}
cookies = {
some-name: "some-value"
some-other-name: "some-other-value"
}
}
The example above will send 4 cookies; name
, another-name
, some-name
, some-other-name
# Request Repeat
Both of the following examples will make 5 post requests with randomized first and last name each time.
# Via argument
request post-example {
method = "POST"
url = "https://httpbin.org/post"
headers = {
"content-type" = "application/json"
}
body = {
firstName = gofakeitFirstName()
lastName = gofakeitLastName()
}
}
$ restbeast request post-example --repeat 5
# Via parameter
request post-example {
method = "POST"
url = "https://httpbin.org/post"
headers = {
"content-type" = "application/json"
}
body = {
firstName = gofakeitFirstName()
lastName = gofakeitLastName()
}
repeat = 5
}