The goal of this project is to facilitate validation of HTTP queries. It provides some end points that can be called with well known behavior and response.
You will java 11 and maven to build the project.
First clone the repository:
$ git clone git@github.com:ypiel-talend/validation-server.git
Then build it:
$ mvn clean install
And start the server:
$ java -jar target/validation-server-0.0.2-SNAPSHOT.jar --server.port=9098
The --server.port
option let you define the port you want to listen to, 8080
is the default.
Once the server is running, you can access its API swagger documentation: http://127.0.0.1:9098/swagger-ui/index.html
You have to build the porject first, then create the docker image:
$ docker build -t validation-server .
[...]
Successfully tagged validation-server:latest
Then execute the container:
$ docker run -p 8080:8080 validation-server
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.7.9)
2023-12-11 14:27:30.779 INFO 1 --- [ main] o.t.c.t.v.ValidationServerApplication : Starting ValidationServerApplication v0.0.2-SNAPSHOT using Java 11.0.16 on ae2f258eaccb with PID 1 (/validation-server-0.0.2-SNAPSHOT.jar started by root in /)
2023-12-11 14:27:30.780 INFO 1 --- [ main] o.t.c.t.v.ValidationServerApplication : No active profile set, falling back to 1 default profile: "default"
2023-12-11 14:27:31.310 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2023-12-11 14:27:31.316 INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2023-12-11 14:27:31.316 INFO 1 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.71]
2023-12-11 14:27:31.359 INFO 1 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2023-12-11 14:27:31.359 INFO 1 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 547 ms
2023-12-11 14:27:31.549 INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2023-12-11 14:27:31.556 INFO 1 --- [ main] o.t.c.t.v.ValidationServerApplication : Started ValidationServerApplication in 1.025 seconds (JVM running for 1.276)
The container port 8080
is mapped on the host also on 8080
, so, the server is ready to be queried from your localhost
.
To enable another container to access the web server, initiate it within the same Docker network. First, identify the network's name of the container that will call the server:
$ docker inspect my_container --format "{{range \$key, \$value := .NetworkSettings.Networks}}{{\$key}} {{end}}"
my_network
So, you have to start the validation server container in this network:
$ docker run --network my_network -p 8080:8080 --name my-validation-server validation-server
[...]
Then, you can identify the server IP
in this network:
$ docker network inspect my_network | jq -r '.[0].Containers[] | select(.Name == "my-validation-server").IPv4Address'
www.xxx.yyy.zzz/mm
Where www.xxx.yyy.zzz
is hte IP
address of the validation server in the network and mm
its subnet mask.
So you can query the web server using this address http://www.xxx.yyy.zzz:8080/....
http://<ip>>:<port>/swagger-ui/index.html
The /ping
endpoint always responds with a pong.
payload. It is just to check if the server is alive.
This endpoint support Accept
header. If Accept: text/plain
then it responds with a plain text response payload:
$ curl -H "Accept: text/plain" http://172.26.0.4:8080/ping
pong.
This endpoint support Accept
header. If Accept: application/json
then it responds with a simple json document:
$ curl -H "Accept: application/json" http://172.26.0.4:8080/ping
{"message":"pong."}
It only supports GET
verb. With any other verb, an error message generated by Sprint
will be returned:
$ curl -X POST -H "Accept: application/json" http://127.0.0.1:8080/ping
{"timestamp":"2024-03-06T20:51:39.177+00:00","status":405,"error":"Method Not Allowed","path":"/ping"}
The /loadfile
endpoint load the file designed by the file
query param and return its content as its payload:
$ echo "Hello" > /tmp/hello.txt
$ echo "the" >> /tmp/hello.txt
$ echo "world!" >> /tmp/hello.txt
$ curl http://127.0.0.1:8080/loadfile?file=/tmp/hello.txt
Hello
the
world!
The /post
endpoint accepts POST
verb and a body. It will return exactly the body it received as plain text or in
a json document, depending the Accept
header:
$ curl -X POST http://127.0.0.1:8080/post --data "Hello world !" -H "Accept: text/plain" -H "Content-Type: text/plain"
Hello world !
The /echo
endpoint do quite the same but returns the received payload in a json object:
$ curl -X POST http://127.0.0.1:8080/post --data "Hello world !" -H "Accept: text/plain" -H "Content-Type: text/plain"
{"post_body":"Hello world !"}
There are two endpoints that deserve paginated elements.
The /paginate
one return a a JSON array that contains some json objects {"id": i, "label": "element_" + i}
. It takes some parameters:
total
: The total number of generated elements.offset
: From which index of element do you want to retrieve them ?limit
: How many elements do you want to retrieve ?
$ curl 'http://127.0.0.1:8080/paginate?total=100&offset=0&limit=5' | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 146 0 146 0 0 48666 0 --:--:-- --:--:-- --:--:-- 48666
[
{
"id": 1,
"label": "element_1"
},
{
"id": 2,
"label": "element_2"
},
{
"id": 3,
"label": "element_3"
},
{
"id": 4,
"label": "element_4"
},
{
"id": 5,
"label": "element_5"
}
]
$ curl 'http://127.0.0.1:8080/paginate?total=100&offset=5&limit=5' | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 148 0 148 0 0 37000 0 --:--:-- --:--:-- --:--:-- 49333
[
{
"id": 6,
"label": "element_6"
},
{
"id": 7,
"label": "element_7"
},
{
"id": 8,
"label": "element_8"
},
{
"id": 9,
"label": "element_9"
},
{
"id": 10,
"label": "element_10"
}
]
The second enpoint is paginateNestedArray
. It takes the same parameters and returns the same elements, but, the element array is a nested array in a root object:
$ curl 'http://127.0.0.1:8080/paginateNestedArray?total=100&offset=0&limit=5' | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 201 0 201 0 0 15461 0 --:--:-- --:--:-- --:--:-- 15461
{
"offset": 0,
"limit": 5,
"total": 100,
"size": 5,
"elements": [
{
"id": 1,
"label": "element_1"
},
{
"id": 2,
"label": "element_2"
},
{
"id": 3,
"label": "element_3"
},
{
"id": 4,
"label": "element_4"
},
{
"id": 5,
"label": "element_5"
}
]
}
$ curl 'http://127.0.0.1:8080/paginateNestedArray?total=100&offset=5&limit=5' | jq .
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 203 0 203 0 0 29000 0 --:--:-- --:--:-- --:--:-- 29000
{
"offset": 5,
"limit": 5,
"total": 100,
"size": 5,
"elements": [
{
"id": 6,
"label": "element_6"
},
{
"id": 7,
"label": "element_7"
},
{
"id": 8,
"label": "element_8"
},
{
"id": 9,
"label": "element_9"
},
{
"id": 10,
"label": "element_10"
}
]
}
There are two endpoints to test HTTP client retry with backoff.
The /retry503
endpoint responds three out of four times a HTTP 503
error with this payload:
{
"error": "You have still to retry '%s' times."
}
The fourth call will return a 200
success HTTP with this one:
{
"success": "true"
}
If you prefer to return a success response before or after the fourth call you can overwrite this with this property -Dvalidation-server.noauth-controller.retry-503-attempts-success=<nb attempts>
.
In the same way, the /retryTimeout
will wait for 3000
milliseconds three out of four
time before responding with a 200
successful HTTP with this payload:
{
"message": "Wait for timeout will be disable in '%s' attempts."
}
The fourth call will not wait before to respond.
The 3000ms
default delay can be overwritten with this propery -Dvalidation-server.noauth-controller.retry-timeout-attempts-delay=<delay>
.
If you prefer to skip the delay before or after the fourth call you can overwrite this with this property -Dvalidation-server.noauth-controller.retry-timeout-attempts-success=<nb attempts>
.
To retrieve a token the query has to be as POST
with header Content-type: x-www-form-urlencoded
and those form key/values:
- client_id = 1234567890
- client_secret = secret_1234567890_
- grant_type = client_credentials
- scope = scA scB scC
$ curl -X POST http://127.0.0.1:8080/oauth2/client-credentials/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'client_id=1234567890' \
-d 'client_secret=secret_1234567890_' \
-d 'grant_type=client_credentials' \
-d 'scope=scA scB scC'
{"access_token":"_success_token_","token_type":"Bearer","expires_in":1702306621631}
The returned token is always the same. On expires_in
is computed dynamically.
If one of those value is wrong, then you should receive such answer:
$ curl -X POST http://127.0.0.1:8080/oauth2/client-credentials/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'client_id=xxxxx' \
-d 'client_secret=xxxxxx' \
-d 'grant_type=client_credentials' \
-d 'scope=scA scB scC'
{"message":"OAuth2 security issue.","cause":"Wrong credentials, can't provide token."}
You can also force the type of expires_in
to be long
(as expected in the OAUTH specification) or String
(as returned by some server):
$ curl -X POST http://127.0.0.1:8080/oauth2/client-credentials/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'client_id=1234567890' \
-d 'client_secret=secret_1234567890_' \
-d 'grant_type=client_credentials' \
-d 'scope=scA scB scC'
-H 'expected_expires_as_string: true'
{"access_token":"_success_token_","token_type":"Bearer","expires_in":"1721731343667"}
You can ask for user
entity in oauth section. It will expect the token:
$ curl -X GET http://127.0.0.1:8080/oauth2/get/user \
-H 'Authorization: Bearer _success_token_'
{"id":1,"name":"Peter","active":true,"alternative":false}
You can give id
, name
and/or active
HTTP query parameter to overwrite the User
attribute value:
$ curl -X GET 'http://127.0.0.1:8080/oauth2/get/user?id=3&name=John&active=false' \
-H 'Authorization: Bearer _success_token_'
{"id":3,"name":"John","active":false,"alternative":false}
If the given token is wrong, an error is returned:
$ curl -X GET http://127.0.0.1:8080/oauth2/get/user \
-H 'Authorization: Bearer _xxxxx_'
{"message":"OAuth2 security issue.","cause":"Unrecognized token."}
The alternative endpoint oauth2/alternative/get/user
is the same as oauth2/get/user
except that the oauth token has to be set in the AlternativeAuthorization
header instead of Authorization
, and, the token prefix is AlternativeTokenPrefix
instead of Bearer
.
If this endpoint is called, the alternative
attribute of the user is set to true
:
$ curl -X GET http://127.0.0.1:9098/oauth2/alternative/get/user \
-H 'AlternativeAuthorization: AlternativeTokenPrefix _success_token_'
{"id":1,"name":"Peter","active":true,"alternative":true}
This project is based on spring boot. You can easily add new endpoints that will suit to your own needs. Then build the docker image and deploy it in your test environment.