apimastery / apisimulator Goto Github PK
View Code? Open in Web Editor NEWAPI Simulator - configuration-driven tool for modeling and running of API simulations
Home Page: https://apisimulator.io
API Simulator - configuration-driven tool for modeling and running of API simulations
Home Page: https://apisimulator.io
As of version 1.9
, it's not possible to provide an external config file, or just augment the default one with a (preferable predefined) key in order to read environment-specific settings.
For instance, when doing callback
s, the hostname will usually change between different environments: test
, qa
, etc.
If the default config file defines a predefined key (e.g.: application
) that we can use for such things, then it would be possible to place any values that we need, maintaining the same (YAML-like structure). Eventually, there has to be a way to read these within the simulations...so that's needed as well ๐คฃ
Something like this:
application: # this is for us, the users ;)
callbacks:
- service: foo
hostname: env.foo.tld
admin: https
host: 1.2.3.4
Actually, that's quite complicated, because the structure for the configuration is not known beforehand, so I'm not sure how to approach this one right away ๐ค
I'm not sure if this is possible, but these days it's very common for an endpoint to provide a stream of data. In order to simulate this, it would require that API Simulator
should be able to keep the connection open while real time data is streamed through an HTTP response/endpoint.
For the most part, I read that there is support for HTTP/2, so I guess this could be doable.
According to the documentation, this is still not supported, but I think it would be great to have this...so just filing an issue here to track this, whenever it gets implemented ๐
Recently, I added API Recorder as an option for a simulation a customer asked for some months ago. Now, they are asking whether the information stored in the *.rec files with the history of requests and responses could be retrieved as an API, specifying for instance the range of dates with the transactions to be displayed. They say that for test automation purposes it would be easier for them to get this information as an API rather than reading or parsing the files.
I searched the documentation and all the examples involving returning data from files only allow you to explicitly select one file, like here:
https://apisimulator.io/docs/1.7/standalone-api-simulator/response-http-codec.html#large-file-download-example
In the Scripting section, I don't see any example or comment saying that it is possible to retrieve the information from multiple files in a simulation folder and parse them on the response. Is it something possible to do currently? If not, are you considering the implementation?
Thank you in advance
Hi! First of all, APISimulator is a very interesting project and I am checking its features to see if I can integrate it into a new project that I am part of!
I am having an issue when using the CSV data provided in the simlet's YAML configuration: I can't match/retrieve the first (data) line of the CSV.
It seems the first data line is ignored when matching the results.
version: "3.9"
services:
csv-apisimulator:
container_name: "csv-apisimulator"
image: apimastery/apisimulator:1.11
ports:
- "6090:6090"
volumes:
- "./templates/csv:/templates"
entrypoint: [ "bash", "-c", "apisimulator start ../../templates"]
# ... other containers definitions ...
templates/csv/apisim.yaml
looks like the following:Note:
The file is a copy + paste from the content (zip file) downloaded from the apisimulator website: apisimulator-http-1.11-distro\apisimulator\apisimulator-http-1.11\examples\parameters-from-csv\simlets\read-product-from-config\simlet.yaml
The only change I made was adding the line:simlet: read-products-csv
#----------------------------------------------------------------------
# Simlet-specific configuration.
#----------------------------------------------------------------------
simlet: read-products-csv
ProductID:
is: parameter
from: uriPathPattern
pattern: "/v1.2/product/{ProductID}/**"
# Parameter names must match the name of the columns in the CSV data
ProductColor:
is: parameter
from: uriPathPattern
pattern: "/v1.2/product/*/{Color}/*"
# Parameter names must match the name of the columns in the CSV data
ProductSize:
is: parameter
from: uriPathPattern
pattern: "/v1.2/product/*/*/{Size}"
ProductData:
is: parameter
from: csv
# The first row in the CSV file is expected to contain unique column names
# with no spaces or special characters in the names other than underscore.
# A column value that contains a comma or double quote(s) is to be surrounded
# by double quotes. Each double quote inside of a column value is to be escaped
# with another double quote. This is what file export to CSV from MS Excel does
data: `
ProductID,ProductCategory,ProductSubcategory,ProductName,ProductColor,ProductSize,ProductSKU,Original_Price,Sale_Price
2706414,Men,Hoodies & Sweatshirts,Fleece Pullover Hoodie,Black Charcoal,S,769409213,55.00,39.99
2706414,Men,Hoodies & Sweatshirts,Fleece Pullover Hoodie,Black Charcoal,M,769413463,55.00,39.99
2706414,Men,Hoodies & Sweatshirts,Fleece Pullover Hoodie,Black Charcoal,L,769413609,55.00,39.99
2706414,Men,Hoodies & Sweatshirts,Fleece Pullover Hoodie,Black Charcoal,XL,769413531,55.00,39.99
2706414,Men,Hoodies & Sweatshirts,Fleece Pullover Hoodie,Black Charcoal,XXL,769413579,55.00,39.99
2706414,Men,Hoodies & Sweatshirts,Fleece Pullover Hoodie,Solid Gray,S,769417959,55.00,35.95
2706414,Men,Hoodies & Sweatshirts,Fleece Pullover Hoodie,Solid Gray,M,769417966,55.00,36.96
2706414,Men,Hoodies & Sweatshirts,Fleece Pullover Hoodie,Solid Gray,L,769413623,55.00,37.97
2706414,Men,Hoodies & Sweatshirts,Fleece Pullover Hoodie,Solid Gray,XL,769413692,55.00,38.98
2706414,Men,Hoodies & Sweatshirts,Fleece Pullover Hoodie,Solid Gray,XXL,769413678,55.00,39.99
2592228,Men,Hoodies & Sweatshirts,Full-Zip Hooded Fleece Jacket,Dark Gray,S,769458440,65.00,52.00
2592228,Men,Hoodies & Sweatshirts,Full-Zip Hooded Fleece Jacket,Dark Gray,M,769458501,65.00,52.00
2592228,Men,Hoodies & Sweatshirts,Full-Zip Hooded Fleece Jacket,Dark Gray,L,769458327,65.00,52.00
2592228,Men,Hoodies & Sweatshirts,Full-Zip Hooded Fleece Jacket,Dark Gray,XL,769458303,65.00,52.00
2592228,Men,Hoodies & Sweatshirts,Full-Zip Hooded Fleece Jacket,Dark Gray,XXL,769458426,65.00,52.00
6218877,Children,Shoes,The "Rockets",White,4,669032116,35.59,31.99
`
# List of keys from parameters to use in querying the CSV data.
# A parameter as key may appear more than once in the list.
# Parameter names must match the column names in the CSV data
keys:
- ProductID
- ProductColor
- ProductSize
request:
- method: GET
- where: uriPathPattern
matches: "/v1.2/product/{id}/{color}/{size}"
- where: parameter
named: ProductData
exists: true
response:
from: template
template: Simula
status: 200
headers:
- "Content-Type: application/json; charset=UTF-8"
body: `
{
"product": {
"id": "${ ProductData['ProductID'] }",
"sku": "${ ProductData['ProductSKU'] }",
"name": "${ jsonEncode( ProductData['ProductName'] ) }",
"category": "${ ProductData['ProductCategory'] }",
"subCategory": "${ ProductData['ProductSubcategory'] }",
"color": "${ ProductData['ProductColor'] }",
"size": "${ ProductData['ProductSize'] }",
"orig_price": ${ ProductData['Original_Price'] },
"sale_price": ${ ProductData['Sale_Price'] }
}
}
`
docker-compose
command:docker-compose up
4.1. Checking for the product in the first line - Result: 404 Not Found
curl:
curl --location 'http://localhost:6090/v1.2/product/2706414/Black Charcoal/S'
HTTP Request and Response
GET /v1.2/product/2706414/Black%20Charcoal/S HTTP/1.1
User-Agent: PostmanRuntime/7.35.0
Accept: */*
Cache-Control: no-cache
Postman-Token: [REDACTED]
Host: localhost:6090
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
HTTP/1.1 404 Not Found
Content-Type: application/text; charset=UTF-8
Server: API Simulator, v1.11
Date: Mon, 27 Nov 2023 14:29:09 GMT
Content-Length: 62
API Simulator couldn't find a matching simlet for the request.
4.2 Checking for the product in the second line - Result: 200 OK
curl:
curl --location 'http://localhost:6090/v1.2/product/2706414/Black Charcoal/M'
HTTP Request and Response:
GET /v1.2/product/2706414/Black%20Charcoal/M HTTP/1.1
User-Agent: PostmanRuntime/7.35.0
Accept: */*
Cache-Control: no-cache
Postman-Token: [REDACTED]
Host: localhost:6090
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Server: API Simulator, v1.11
Date: Mon, 27 Nov 2023 14:31:36 GMT
Content-Length: 270
{
"product": {
"id": "2706414",
"sku": "769413463",
"name": "Fleece Pullover Hoodie",
"category": "Men",
"subCategory": "Hoodies & Sweatshirts",
"color": "Black Charcoal",
"size": "M",
"orig_price": 55.00,
"sale_price": 39.99
}
}
If I change the places of the "S" product to the second line, and the "M" product to the first line, then the "M" (medium) product is the one that is not matched:
ProductID,ProductCategory,ProductSubcategory,ProductName,ProductColor,ProductSize,ProductSKU,Original_Price,Sale_Price
2706414,Men,Hoodies & Sweatshirts,Fleece Pullover Hoodie,Black Charcoal,M,769413463,55.00,39.99
2706414,Men,Hoodies & Sweatshirts,Fleece Pullover Hoodie,Black Charcoal,S,769409213,55.00,39.99
So, the behavior seems tied to the first data line of the csv configuration.
2023-11-27T14:27:50.284+0000|INFO|10|main|[+--------------------------+]
2023-11-27T14:27:50.285+0000|INFO|10|main|[| A P I S i m u l a t o r |]
2023-11-27T14:27:50.285+0000|INFO|10|main|[+--------------------------+]
2023-11-27T14:27:50.291+0000|INFO|10|main|[API Simulator initialization in progress...]
2023-11-27T14:27:50.291+0000|INFO|10|main|[API Simulator: {java version:"11.0.19+7", jvm name:"OpenJDK 64-Bit Server VM", jvm version:"11.0.19+7", jvm pid:10}]
2023-11-27T14:27:50.291+0000|INFO|10|main|[API Simulator: implementation {title:"API Simulator",version:"1.11"}]
2023-11-27T14:27:50.291+0000|INFO|10|main|[API Simulator: args=[-c, /apisimulator/apisimulator-http-1.11/config, -s, /templates/simlets, /templates]]
2023-11-27T14:27:50.305+0000|INFO|10|main|[Loading configuration from '/apisimulator/apisimulator-http-1.11/config/apisim.yaml'...]
2023-11-27T14:27:50.338+0000|INFO|10|main|[Loaded admin configuration]
2023-11-27T14:27:50.342+0000|INFO|10|main|[Loaded simulator configuration]
2023-11-27T14:27:50.346+0000|INFO|10|main|[Loaded simulation configuration]
2023-11-27T14:27:50.374+0000|INFO|10|main|[Loaded 2 simlets in 69 ms]
2023-11-27T14:27:50.376+0000|INFO|10|main|[Loading configuration from '/templates/apisim.yaml'...]
2023-11-27T14:27:51.410+0000|INFO|10|main|[Loaded 1 simlet in 1034 ms]
2023-11-27T14:27:51.411+0000|INFO|10|main|[Loading simlets from '/templates/simlets'...]
2023-11-27T14:27:51.413+0000|INFO|10|main|[Loaded 0 simlets in 1 ms]
2023-11-27T14:27:51.783+0000|INFO|10|API-Simulator-Boss-1-1|[API Simulator Server is listening on 0.0.0.0:6090]
2023-11-27T14:27:51.783+0000|INFO|10|main|[API Simulator started]
2023-11-27T14:27:51.802+0000|INFO|10|AdminServer-Boss-6-1|[Admin HTTP Server is listening on 0.0.0.0:6190]
Please let me know if any other information is needed!
Thanks!
Our simlets are organized in folders.
We have two folders named :
api-simulator/simlets/register/check-reference
api-simulator/simlets/reset-credentials/check-reference
and we get a 404 (API Simulator doesn't find a simlet for...") on the first one. It works fine when we rename one of them.
We think it's an issue because our routes in our simlet.yaml are diferent.
I have setup some simlets runing on my localhost at a specfic port. When I am trying to perform a simple load of 100 users hitting the simulated API, I am getting below error intermittently. Error percentage varries from 0 to 70% in various runs. Can someone please help here, if I need to change some settings for APISimulator or something else.
java.net.BindException: Address already in use: connect
at java.base/sun.nio.ch.Net.connect0(Native Method)
at java.base/sun.nio.ch.Net.connect(Net.java:579)
at java.base/sun.nio.ch.Net.connect(Net.java:568)
at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:585)
at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:327)
at java.base/java.net.Socket.connect(Socket.java:633)
at org.apache.http.conn.socket.PlainConnectionSocketFactory.connectSocket(PlainConnectionSocketFactory.java:75)
at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142)
at org.apache.jmeter.protocol.http.sampler.HTTPHC4Impl$JMeterDefaultHttpClientConnectionOperator.connect(HTTPHC4Impl.java:409)
at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:376)
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:393)
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186)
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
at org.apache.jmeter.protocol.http.sampler.HTTPHC4Impl.executeRequest(HTTPHC4Impl.java:940)
at org.apache.jmeter.protocol.http.sampler.HTTPHC4Impl.sample(HTTPHC4Impl.java:651)
at org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy.sample(HTTPSamplerProxy.java:66)
at org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase.sample(HTTPSamplerBase.java:1311)
at org.apache.jmeter.protocol.http.sampler.HTTPSamplerBase.sample(HTTPSamplerBase.java:1300)
at org.apache.jmeter.threads.JMeterThread.doSampling(JMeterThread.java:651)
at org.apache.jmeter.threads.JMeterThread.executeSamplePackage(JMeterThread.java:570)
at org.apache.jmeter.threads.JMeterThread.processSampler(JMeterThread.java:501)
at org.apache.jmeter.threads.JMeterThread.run(JMeterThread.java:268)
at java.base/java.lang.Thread.run(Thread.java:833)
Template Functions are great. The library reference for random
offers several functions, but I think a good addition to it could be something like com.github.javafaker:javafaker
in order to get meaningful values for common return types, like: addresses, states, first names, last names, credit card numbers, etc.
I'm aware something similar can be achieved by using a random token, but it's not quite the same. This would be similar to what Postman already have implemented: https://learning.postman.com/docs/writing-scripts/script-references/variables-list/
I'm not sure if this is possible today, but I haven't find a meaningful way to see the matched simulations whenever a request is sent. This is one example of what MockServer will print in the logs:
11-06-2020 19:16:17,676 |- INFO in o.m.log.MockServerEventLog:108 [MockServer-EventLog0] - 1080 received request:
{
"method" : "GET",
"path" : "/api/characters/c6d1413f-84c2-4781-b0ee-47c934600ee6",
"headers" : {
"Accept" : [ "application/json" ],
"User-Agent" : [ "PostmanRuntime/7.26.3" ],
"Host" : [ "localhost:1080" ],
"Accept-Encoding" : [ "gzip, deflate, br" ],
"Connection" : [ "keep-alive" ],
"content-length" : [ "0" ]
},
"keepAlive" : true,
"secure" : false
}
11-06-2020 19:16:17,677 |- INFO in o.m.log.MockServerEventLog:108 [MockServer-EventLog0] - 1080 request:
{
"method" : "GET",
"path" : "/api/characters/c6d1413f-84c2-4781-b0ee-47c934600ee6",
"headers" : {
"Accept" : [ "application/json" ],
"User-Agent" : [ "PostmanRuntime/7.26.3" ],
"Host" : [ "localhost:1080" ],
"Accept-Encoding" : [ "gzip, deflate, br" ],
"Connection" : [ "keep-alive" ],
"content-length" : [ "0" ]
},
"keepAlive" : true,
"secure" : false
}
didn't match expectation:
Expectation[hashCode=225248708,httpError=<null>,httpForward=<null>,httpForwardClassCallback=<null>,httpForwardObjectCallback=<null>,httpForwardTemplate=<null>,httpOverrideForwardedRequest=<null>,httpRequest=HttpRequest[body=JsonSchemaBody[hashCode=748364778,jsonSchema=schemas/create-character.json,parameterStyles=<null>,hashCode=1887080696,optional=<null>,type=JSON_SCHEMA,hashCode=31,not=<null>],cookies=<null>,hashCode=-550189459,headers={
"accept" : [ "application/json" ],
"content-type" : [ "application/json" ]
},keepAlive=<null>,method=POST,path=/api/characters,pathParameters=<null>,queryStringParameters=<null>,secure=<null>,socketAddress=<null>,logCorrelationId=<null>,hashCode=31,not=<null>],httpResponse={
"statusCode" : 201,
"headers" : {
"location" : [ "/api/characters/0f578684-3221-43b5-af31-7331ea55fbd2" ]
}
},httpResponseClassCallback=<null>,httpResponseObjectCallback=<null>,httpResponseTemplate=<null>,priority=0,timeToLive=TimeToLive.1[hashCode=31022,timeToLive=<null>,timeUnit=<null>,unlimited=true],times=Times.1[hashCode=2161,remainingTimes=-1,unlimited=true]]
because:
method didn't match
11-06-2020 19:16:17,678 |- INFO in o.m.log.MockServerEventLog:108 [MockServer-EventLog0] - 1080 request:
{
"method" : "GET",
"path" : "/api/characters/c6d1413f-84c2-4781-b0ee-47c934600ee6",
"headers" : {
"Accept" : [ "application/json" ],
"User-Agent" : [ "PostmanRuntime/7.26.3" ],
"Host" : [ "localhost:1080" ],
"Accept-Encoding" : [ "gzip, deflate, br" ],
"Connection" : [ "keep-alive" ],
"content-length" : [ "0" ]
},
"keepAlive" : true,
"secure" : false
}
didn't match expectation:
{
"id" : "1002. Retrieve (Nonexistent) Character",
"priority" : 0,
"httpRequest" : {
"method" : "GET",
"path" : "/api/characters/4d33ad25-96a9-47d8-b552-4dd2c4c719d1",
"headers" : {
"accept" : [ "application/json" ]
}
},
"times" : {
"unlimited" : true
},
"timeToLive" : {
"unlimited" : true
},
"httpResponse" : {
"statusCode" : 404,
"headers" : {
"content-type" : [ "application/json" ]
},
"body" : {
"type" : "JSON",
"json" : {
"timestamp" : 1500597044204,
"status" : 404,
"error" : "Not Found",
"message" : null,
"path" : "/api/characters/4d33ad25-96a9-47d8-b552-4dd2c4c719d1"
}
}
}
}
because:
method matched
path didn't match
11-06-2020 19:16:17,678 |- INFO in o.m.log.MockServerEventLog:108 [MockServer-EventLog0] - 1080 request:
{
"method" : "GET",
"path" : "/api/characters/c6d1413f-84c2-4781-b0ee-47c934600ee6",
"headers" : {
"Accept" : [ "application/json" ],
"User-Agent" : [ "PostmanRuntime/7.26.3" ],
"Host" : [ "localhost:1080" ],
"Accept-Encoding" : [ "gzip, deflate, br" ],
"Connection" : [ "keep-alive" ],
"content-length" : [ "0" ]
},
"keepAlive" : true,
"secure" : false
}
matched expectation:
{
"id" : "1003. Retrieve (Existing) Character",
"priority" : 0,
"httpRequest" : {
"method" : "GET",
"path" : "/api/characters/[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}",
"headers" : {
"accept" : [ "application/json" ]
}
},
"times" : {
"unlimited" : true
},
"timeToLive" : {
"unlimited" : true
},
"httpResponse" : {
"statusCode" : 200,
"headers" : {
"content-type" : [ "application/json" ]
},
"body" : {
"type" : "JSON",
"json" : {
"first_name" : "Guybrush",
"last_name" : "Threepwood",
"age" : 38,
"_id" : "92ee8641-f7b3-4753-9966-7c4dfab4a591"
}
}
}
}
11-06-2020 19:16:17,678 |- INFO in o.m.log.MockServerEventLog:108 [MockServer-EventLog0] - 1080 returning response:
{
"statusCode" : 200,
"headers" : {
"content-type" : [ "application/json" ]
},
"body" : {
"first_name" : "Guybrush",
"last_name" : "Threepwood",
"age" : 38,
"_id" : "92ee8641-f7b3-4753-9966-7c4dfab4a591"
}
}
for request:
{
"method" : "GET",
"path" : "/api/characters/c6d1413f-84c2-4781-b0ee-47c934600ee6",
"headers" : {
"Accept" : [ "application/json" ],
"User-Agent" : [ "PostmanRuntime/7.26.3" ],
"Host" : [ "localhost:1080" ],
"Accept-Encoding" : [ "gzip, deflate, br" ],
"Connection" : [ "keep-alive" ],
"content-length" : [ "0" ]
},
"keepAlive" : true,
"secure" : false
}
for action:
{
"statusCode" : 200,
"headers" : {
"content-type" : [ "application/json" ]
},
"body" : {
"first_name" : "Guybrush",
"last_name" : "Threepwood",
"age" : 38,
"_id" : "92ee8641-f7b3-4753-9966-7c4dfab4a591"
}
}
from expectation:
1003. Retrieve (Existing) Character
As shown, it's easier to debug when something doesn't match and things like that. I think that could be a great addition to the logs, and even so, doing something like what WireMock does is even better. Those folks will display the closest (simulation/expectation/mock) match according to the request send, and what was/were the field(s) that didn't match โ hence the corresponding response was not sent.
I've read the Body Matching section. I'm aware something similar can be achieved by using JSONPath, but the outcome won't be the same. Moreover, teams already employ JSON schemas or Open API definitions to check end-point contracts โ so that's a time saver.
I think it would be great is API Simulator could validate a given request body against a corresponding JSON schema. For instance, MockServer does something similar:
...
"body": {
"type": "JSON_SCHEMA",
"jsonSchema": "schemas/create-entity.schema.json"
},
...
I am trying to enable https connection on the APISimulator but with no luck, I had create new keystore and configure it in the tls section on the apisim.yaml file for under simulator: http but when I call the Simulation of the API I had developed using Postman API caller I'm getting error as below:
"Error: write EPROTO 3578294616:error:10000410:SSL routines:OPENSSL_internal:SSLV3_ALERT_HANDSHAKE_FAILURE:../../third_party/boringssl/src/ssl/tls_record.cc:592:SSL alert number 40 3578294616:error:1000009a:SSL routines:OPENSSL_internal:HANDSHAKE_FAILURE_ON_CLIENT_HELLO:../../third_party/boringssl/src/ssl/handshake.cc:596"
can you help?
I have a simulation that looks very much like this:
---
simlet: create-character
# ...parameters
character_id:
from: uuid
is: parameter
request:
- header: accept
equals: application/json
- header: content-type
equals: application/json
- method: POST
- uriPath: /api/characters
response:
callback: # https://apisimulator.io/docs/latest/standalone-api-simulator/callback.html
latency:
random:
max: 1000
min: 250
timeUnit: milliseconds
http1:
body:
text: |-
{
"subscriber_id": ${character_id}
# ...why we cannot read from JSON request body ๐ญ
}
type: text
from: template
headers:
- name: accept
value: application/json
- name: content-type
value: application/json
method: POST
template: Simula
uri:
host: https://api-simulator.free.beeceptor.com
path: /api/v1/subscriptions
scheme: https
from: template
headers:
- name: location
value: ${_request.uri.path.value}/${character_id}
status: 201
template: Simula
This fails with the following error message:
2021-10-06T01:56:49.368+0000|INFO|11|main|[+--------------------------+]
2021-10-06T01:56:49.371+0000|INFO|11|main|[| A P I S i m u l a t o r |]
2021-10-06T01:56:49.371+0000|INFO|11|main|[+--------------------------+]
2021-10-06T01:56:49.373+0000|INFO|11|main|[API Simulator initialization in progress...]
2021-10-06T01:56:49.374+0000|INFO|11|main|[API Simulator: {java version:"11.0.10+9", jvm name:"OpenJDK 64-Bit Server VM", jvm version:"11.0.10+9", jvm pid:11}]
2021-10-06T01:56:49.374+0000|INFO|11|main|[API Simulator: implementation {title:"API Simulator",version:"1.9"}]
2021-10-06T01:56:49.374+0000|INFO|11|main|[API Simulator: args=[-c, /apisimulator/apisimulator-http-1.9/config, -s, /home/apisimulator/simulations/simlets, /home/apisimulator/simulations, -retain_dsl_models]]
2021-10-06T01:56:49.387+0000|INFO|11|main|[Loading configuration from '/apisimulator/apisimulator-http-1.9/config/apisim.yaml'...]
2021-10-06T01:56:49.427+0000|INFO|11|main|[Loaded admin configuration]
2021-10-06T01:56:49.434+0000|INFO|11|main|[Loaded simulator configuration]
2021-10-06T01:56:49.438+0000|INFO|11|main|[Loaded simulation configuration]
2021-10-06T01:56:49.468+0000|INFO|11|main|[Loaded 2 simlets in 81 ms]
2021-10-06T01:56:49.469+0000|INFO|11|main|[Loading simlets from '/home/apisimulator/simulations/simlets'...]
2021-10-06T01:56:49.948+0000|INFO|11|main|[Loaded 2 simlets in 478 ms]
2021-10-06T01:56:50.402+0000|INFO|11|API-Simulator-Boss-1-1|[API Simulator Server is listening on 0.0.0.0:6090]
2021-10-06T01:56:50.403+0000|INFO|11|main|[API Simulator started]
2021-10-06T01:56:50.438+0000|INFO|11|AdminServer-Boss-6-1|[Admin HTTP Server is listening on 0.0.0.0:6190]
2021-10-06T01:56:57.446+0000|ERROR|11|API-Simlet-3-1|[com.apisimulator.callback.HttpCallbackExecutor.submit(HttpClient, HttpCallbackSpec, HttpRequest): got an exception: com.apisimulator.http.client.HttpClientException: com.apisimulator.http.client.HttpClientException: com.apisimulator.http.netty.HttpClientBase.bootstrapAndConnect(ChannelHandler channelInitializer): Connecting to https://api-simulator.free.beeceptor.com:443 failed: java.net.UnknownHostException: https://api-simulator.free.beeceptor.com: Name or service not known]
A cURL
request to the same URI succeeds:
curl --header "accept: application/json" --header "content-type: application/json" --request POST https://api-simulator.free.beeceptor.com/api/v1/subscriptions | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 16 0 16 0 0 6 0 --:--:-- 0:00:02 --:--:-- 6
{
"ok": true
}
You can verify the requests by going to: https://beeceptor.com/console/api-simulator
โ before start sending them.
Imagine there is a simulation like this one (excerpt):
---
simlet: Identifier
parameters: # <- why is this going away if it's super-useful to organize all parameters under one key!
- age:
from: number
range:
min: 20
max: 40
request:
...
response:
from: template
template: Simula
headers:
- name: content-type
value: "application/json"
status: 200
body:
file: "${sim.path}/umbrella-api/operation-one/files/response/model-identifier/get-response.json"
...and get-response.json
could be something like:
{
"first_name": "Rahy",
"last_name": "Hellbleeder",
"age": "${age}", !!!
"_id": "${_random.uuid()}"
}
As you can notice by the !!!
, age
is being sent as a string
, instead of a number
. The reason it needs to be wrapped into double quotes is to make get-response.json
a valid JSON.
It works fine if the double quotes are removed, but then auto-formatting and some checks placed on the project's pipeline for JSON/YAML files will fail.
Is there a work-around for this?
While reading the Using External Files I was under the impression that simlets.path
will resolve to the actual running simulation path โ now I know that's not the case ๐
I'm aware that it's not a big deal, but it would be great if there is a variable like those (e.g.: simlet.path
) that refers to the actual running simulation/simlet. Basically, the variable can resolve to the PWD
of a given simlet.yaml
that's being run/resolved when a request matches it.
I'm using API Simulator 1.7
, Docker 19.03.13-ce, build 4484c46d9d
, and Docker Compose 1.27.4
.
Im trying to setup the logging using APISIMULATOR_LOG_FILE
, but it looks like it doesn't have any impact when I start the container.
This is my docker-compose.yml
file:
version: "3.8"
services:
simulator:
command: ["apisimulator", "start", "/apisims/stubs"]
container_name: api-simulator
environment:
APISIMULATOR_LOG_FILE: config/log4j2.xml
image: docker.io/apimastery/apisimulator:1.7 # https://registry.hub.docker.com/r/apimastery/apisimulator/
networks:
- global-network
ports:
- "6090:6090"
restart: on-failure
user: "${USER}:${GROUP}"
volumes:
- "${PWD}/stubs:/apisims/stubs"
networks:
global-network:
driver: bridge
name: api-simulator-network
...and this is my log4j2.xml
configuration file:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration monitorInterval="30">
<Properties>
<Property name="log-pattern">%d{MM-dd-yyyy HH:mm:ss.SSS} |- %highlight{%5p}{trace=blue, debug=green, info=green, warn=yellow, error=red, fatal=red} in %style{%C{1}:%L}{cyan} [%style{%t}{magenta}] - %m%n</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="${log-pattern}" />
</Console>
</Appenders>
<!-- Logger levels: all, trace, debug, info, warn, error, fatal, off -->
<Loggers>
<AsyncLogger name="com.apisimulator" level="debug" additivity="false" includeLocation="true">
<AppenderRef ref="Console" />
</AsyncLogger>
<Root level="warn">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>
When I start the container, all I can see is this:
$ docker logs --follow api-simulator
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.codehaus.groovy.reflection.CachedClass (file:/apisimulator/apisimulator-http-1.7/lib/groovy-2.5.10.jar) to method java.lang.Object.finalize()
WARNING: Please consider reporting this to the maintainers of org.codehaus.groovy.reflection.CachedClass
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
But there is a file apisimulator.log
that always gets created under my stubs
directory with a similar content all the time:
2020-10-11T11:13:03.826+0000|INFO|9|main|[+--------------------------+]
2020-10-11T11:13:03.827+0000|INFO|9|main|[| A P I S i m u l a t o r |]
2020-10-11T11:13:03.827+0000|INFO|9|main|[+--------------------------+]
2020-10-11T11:13:03.828+0000|INFO|9|main|[com.apisimulator.launcher.AbstractLaunchable.init(String[] args): initialization in progress...]
2020-10-11T11:13:03.828+0000|INFO|9|main|[com.apisimulator.launcher.AbstractLaunchable.init(String[] args): {java version:"11.0.8+10", jvm name:"OpenJDK 64-Bit Server VM", jvm version:"11.0.8+10", jvm pid:9}]
2020-10-11T11:13:03.828+0000|INFO|9|main|[com.apisimulator.launcher.AbstractLaunchable.init(String[] args): implementation {title:"HTTP API Simulator",version:"1.7"}]
2020-10-11T11:13:03.828+0000|INFO|9|main|[com.apisimulator.launcher.AbstractLaunchable.init(String[] args): args=[-c, /apisimulator/apisimulator-http-1.7/config, -s, /apisims/stubs/simlets, /apisims/stubs]]
2020-10-11T11:13:03.855+0000|INFO|9|main|[com.apisimulator.config.AppConfigLoader is looking in and under /apisims/stubs/simlets]
2020-10-11T11:13:04.228+0000|INFO|9|main|[com.apisimulator.config.AppConfigLoader loaded 1 file in 373 ms]
2020-10-11T11:13:04.349+0000|INFO|9|main|[com.apisimulator.config.SingleYamlConfigLoader.load(): loaded 2 simlets from '/apisimulator/apisimulator-http-1.7/config/apisim.yaml' in 120 ms]
2020-10-11T11:13:04.495+0000|INFO|9|main|[com.apisimulator.netty.tls.TlsHandlerFactorySupport: ALPN=true; OpenSSL=true]
2020-10-11T11:13:04.749+0000|INFO|9|API-Simulator-Boss-4-1|[com.apisimulator.netty.NettySimulatorServer.start(): listening on 0.0.0.0:6090]
2020-10-11T11:13:04.751+0000|INFO|9|main|[com.apisimulator.launcher.AbstractLaunchable.start(): API Simulator started]
2020-10-11T11:13:04.752+0000|INFO|9|main|[com.apisimulator.launcher.AdminServer: listening on port #6190]
Teams continues to evolve and adopt modern technologies for efficient cross-service communication. In recent years, gRPC has gained significant traction as a high-performance, language-agnostic framework for building distributed systems.
In that regard, I would like to propose to incorporate gRPC support in APISimulator. This enhancement will align APISimulator with industry best practices (and trends ๐), allowing to efficiently develop, test, and debug services that rely on gRPC endpoints.
Thank you for considering this!
Dear @apisim ,
Am trying to do some matching operations using equals and some other ones you can find attached my sample simulation.
The simulation should be done as below:
Hi!
I've been trying the feature documented here SQL Data Store.
When I tested with the H2 database, the feature worked as expected.
But, when I tried other driver classes, I got errors:
When trying to connect to MS SQL Server
java.lang.IllegalStateException: Failed to load JDBC driver class 'com.microsoft.sqlserver.jdbc.SQLServerDriver']
A failure also happened when trying to use org.postgresql.Driver
driver class.
The configuration used:
ProductData:
is: parameter
from: sql
connection:
# Currently, pooled connections are not supported
uses: driver # pool
# Driver's fully-qualified class name
driver: "com.microsoft.sqlserver.jdbc.SQLServerDriver"
...
So, my question is: Does the SQL Data Store feature support other databases than H2?
Thanks!
API Simulator has a way to acquire the value from the request body by using "parameters", for instance:
ProductID:
element: ".product.id"
from: body
is: parameter
Is it possible to support this "on the fly"? For instance, we can do ${_request.body.product.id}
directly, because we are only interested on this value to put it in a template, but not to use it on a matcher.
The way API Simulator
loads the simulations is pretty handy, but once they are loaded and more changes are needed on a given (set of) simulation(s), it needs to be restarted to be able to pick up the changes.
It would be great if there is an end-point, e.g.: __admin/simulations/reload
, that can be POST
ed to reload/reset all the changes without stopping API Simulator
.
As a bonus, it would be also nice to get all the defined simulations, e.g.:
__admin/simulations
.
Say for instance I have a CSV file with data used throughout a set of resources. Currently, I can't find a way to build JSON responses based on (the entire or a subset of) the data. I think probably using the Simula Template Scriptlets
.
Would it be possible to enhance the DSL to accomplish such tasks with lists/iterators?
An example might make things more clear:
simlet: retrieve-people
# ...parameters
character:
file: ${simlets.path}/people.csv
from: csv
is: parameter
keys:
- person_id
request:
- header: accept
equals: application/json
- method: GET
- uriPathPattern: /api/people
response:
body:
file: ${simlet.path}/people.response.json
from: template
headers:
- name: content-type
value: application/json
status: 200
template: Simula
person_id,first_name,last_name
19b766af-43e5-4695-8593-452b62fdf361,Smith,Johnson
3bd23f11-e668-4c3f-ba74-3d45e6033c46,Arabella,Foster
baf5dade-eabd-4d54-98f7-e90f6953108f,Quentin,Kelley
[
{
"first_name": "${person['first_name']}",
"last_name": "${person['last_name']}",
"_id": "${person_id}"
},
]
Currently the Docker
images uses Debian 11 (bullseye)
and OpenJDK
.
A better alternative is to move towards Eclipse Temurin
โ and probably reduce the image footprint by using Alpine
instead of Debian
.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.