diff --git a/.gitignore b/.gitignore index 895439e..264cd49 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ workspace/ *.r src/out.log +client.log **/.DS_Store docker/._* diff --git a/README.md b/README.md index c83df87..5ffc742 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,22 @@ # HttpClientForOpenEdge Http client for OpenEdge with ABL and Dotnet implementations. +# Install / Get started + +Build image (in `docker` dir) +`build-image.sh` +This creates a symbolic link in `/etc/ssl/certs` to `/usr/bin/certs/84736cea.0`, so also .NET has access to this certificate after the container has run (see below) + +Start Caddy webservice with petstore (in `petstore-local` dir) +`start-caddy.sh` +This also extracts the caddy ssl certificate and stores it as: `src/certs/84736cea.0` +Creates the `appnet` network +Swagger is now available on: http://localhost:8080/ + +Run docker container for the first time (in `docker` dir) +`run-tty.sh` +This copies the certificates to the `/usr/dlc/certs` dir which is also linked to `/etc/ssl/certs` dir for .NET +It attaches to the `appnet` network + +Or when the container already exits (in `docker` dir) +`start-tty.sh` diff --git a/docker/Dockerfile b/docker/Dockerfile index 13a5366..708ae4b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -4,4 +4,10 @@ USER root RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && update-ca-certificates +# also install 'curl' to test network connections +RUN apt-get update && apt-get install -y curl + +# already create a symbolic link for the caddy certificate +RUN ln -s /usr/dlc/certs/84736cea.0 /etc/ssl/certs/84736cea.0 + USER openedge diff --git a/docker/build-image.bat b/docker/build-image.bat index d125c9c..654c99f 100644 --- a/docker/build-image.bat +++ b/docker/build-image.bat @@ -1,5 +1,5 @@ -@ECHO OFF +@echo off -IMAGE=flusso/http-client-pug-image +image=flusso/http-client-pug-image -docker build --rm --platform linux/amd64 -t %IMAGE% . +docker build --rm --platform linux/amd64 -t %image% . diff --git a/docker/run-tty.bat b/docker/run-tty.bat index 5fef65e..5eb386f 100644 --- a/docker/run-tty.bat +++ b/docker/run-tty.bat @@ -1,21 +1,22 @@ -@ECHO OFF +@echo off -set IMAGE=flusso/http-client-pug-image -set CONTAINER=http-client-pug-container +set image=flusso/http-client-pug-image +set container=http-client-pug-container -echo "Stop container %CONTAINER% ..." -docker stop %CONTAINER% +echo "Stop container %container% ..." +docker stop %container% -echo "Remove container %CONTAINER% ..." -docker rm %CONTAINER% +echo "Remove container %container% ..." +docker rm %container% echo "Run new container (interactive) ..." docker run -it ^ - --name %CONTAINER% ^ + --name %container% ^ + --network appnet ^ --platform linux/amd64 ^ - -v %CD%/../license/progress.cfg:/usr/dlc/progress.cfg ^ - -v %CD%/../src:/app/src ^ - -v %CD%/../assemblies:/app/assemblies ^ - -v %CD%/../config:/app/config ^ - %IMAGE% ^ - bash + -v %cd%/../license/progress.cfg:/usr/dlc/progress.cfg ^ + -v %cd%/../src:/app/src ^ + -v %cd%/../assemblies:/app/assemblies ^ + -v %cd%/../config:/app/config ^ + %image% ^ + bash -c "cp -p /app/src/certs/* /usr/dlc/certs/ && exec bash" diff --git a/docker/run-tty.sh b/docker/run-tty.sh index f8d5561..4559b82 100755 --- a/docker/run-tty.sh +++ b/docker/run-tty.sh @@ -12,10 +12,11 @@ docker rm ${container} echo "Run new container (interactive) ..." docker run -it \ --name ${container} \ + --network appnet \ --platform linux/amd64 \ -v $(pwd)/../license/progress.cfg:/usr/dlc/progress.cfg \ -v $(pwd)/../src:/app/src \ -v $(pwd)/../assemblies:/app/assemblies \ -v $(pwd)/../config:/app/config \ ${image} \ - bash + bash -c "cp -p /app/src/certs/* /usr/dlc/certs/ && exec bash" diff --git a/docker/start-tty.bat b/docker/start-tty.bat index e64e67b..be33b0a 100644 --- a/docker/start-tty.bat +++ b/docker/start-tty.bat @@ -1,6 +1,6 @@ -@ECHO OFF +@echo off -set CONTAINER=http-client-pug-container +set container=http-client-pug-container echo "Start container (interactive) ..." @@ -8,4 +8,4 @@ docker ps echo -docker start -i %CONTAINER% +docker start -i %container% diff --git a/petstore-local/Caddyfile b/petstore-local/Caddyfile new file mode 100644 index 0000000..a3e9a7b --- /dev/null +++ b/petstore-local/Caddyfile @@ -0,0 +1,18 @@ +https://localhost:8443 { + tls internal # Caddy maakt automatisch een lokaal (self-signed) cert + encode gzip # (optioneel) compressie + reverse_proxy petstore:8080 +} + +https://caddy:8443 { + tls internal # Caddy maakt automatisch een lokaal (self-signed) cert + encode gzip # (optioneel) compressie + reverse_proxy petstore:8080 +} + +# voor Parallels windows +https://caddy.lan:8443 { + tls internal # Caddy maakt automatisch een lokaal (self-signed) cert + encode gzip # (optioneel) compressie + reverse_proxy petstore:8080 +} diff --git a/petstore-local/docker-compose.yml b/petstore-local/docker-compose.yml new file mode 100644 index 0000000..50c13a8 --- /dev/null +++ b/petstore-local/docker-compose.yml @@ -0,0 +1,29 @@ +services: + petstore: + image: swaggerapi/petstore + container_name: petstore + # (optioneel) ook via HTTP direct testen: + ports: + - "8080:8080" + networks: [appnet] + + caddy: + image: caddy:2-alpine + container_name: caddy + depends_on: + - petstore # niet wachten op 'healthy' + ports: + - "8443:8443" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - caddy_data:/data + - caddy_config:/config + networks: [appnet] + +volumes: + caddy_data: + caddy_config: + +networks: + appnet: + name: appnet diff --git a/petstore-local/readme.txt b/petstore-local/readme.txt new file mode 100644 index 0000000..31630c0 --- /dev/null +++ b/petstore-local/readme.txt @@ -0,0 +1,31 @@ +Valideer je compose-bestand vóór je start: + docker compose config + +Docker draaien (detached mode): + docker compose up -d + +Met recreate: + docker-compose up -d --force-recreate caddy + +Docker kijken of alles draait: + docker compose ps + +Docker down brengen: + docker compose down --remove-orphans + +Testen: + Swagger interface: + http://localhost:8080/ + + HTTP GET: + curl -X 'GET' http://localhost:8080/api/store/inventory + + HTTPS GET: + Server certificate ophalen: + docker exec caddy sh -c 'cat /data/caddy/pki/authorities/local/root.crt' > caddy-root.crt + Certificate meegeven (of anders in cert store opslaan) + curl -X 'GET' --cacert caddy-root.crt https://localhost:8443/api/store/inventory + +============== + +Meer over certificaten, zie mail d.d. 17-04-2025: OpenEdge HttpClient certificates diff --git a/petstore-local/start-caddy.bat b/petstore-local/start-caddy.bat new file mode 100755 index 0000000..e1646d7 --- /dev/null +++ b/petstore-local/start-caddy.bat @@ -0,0 +1,8 @@ +@echo off + +docker compose up -d + +# download the certificate +docker exec caddy sh -c 'cat /data/caddy/pki/authorities/local/root.crt' > ..\src\certs\84736cea.0 + +dir ..\src\certs\84736cea.0 diff --git a/petstore-local/start-caddy.sh b/petstore-local/start-caddy.sh new file mode 100755 index 0000000..8c378b3 --- /dev/null +++ b/petstore-local/start-caddy.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +docker compose up -d + +# download the certificate +docker exec caddy sh -c 'cat /data/caddy/pki/authorities/local/root.crt' > ../src/certs/84736cea.0 + +ls -l ../src/certs/84736cea.0 diff --git a/petstore-local/stop-caddy.bat b/petstore-local/stop-caddy.bat new file mode 100755 index 0000000..12340a6 --- /dev/null +++ b/petstore-local/stop-caddy.bat @@ -0,0 +1,3 @@ +@echo off + +docker compose down diff --git a/petstore-local/stop-caddy.sh b/petstore-local/stop-caddy.sh new file mode 100755 index 0000000..1c75693 --- /dev/null +++ b/petstore-local/stop-caddy.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +docker compose down diff --git a/src/certs/84736cea.0 b/src/certs/84736cea.0 new file mode 100644 index 0000000..a665f75 --- /dev/null +++ b/src/certs/84736cea.0 @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBozCCAUqgAwIBAgIRAMC/AkerJ7H+V5tcLaxOvGswCgYIKoZIzj0EAwIwMDEu +MCwGA1UEAxMlQ2FkZHkgTG9jYWwgQXV0aG9yaXR5IC0gMjAyNSBFQ0MgUm9vdDAe +Fw0yNTA5MjQwNzE0MzJaFw0zNTA4MDMwNzE0MzJaMDAxLjAsBgNVBAMTJUNhZGR5 +IExvY2FsIEF1dGhvcml0eSAtIDIwMjUgRUNDIFJvb3QwWTATBgcqhkjOPQIBBggq +hkjOPQMBBwNCAATUSTVg6M1nfxUmuErrTv8rKZ7oS01L8G+LjIVrg61i+vEXllep +vculRhdLzSohXGE/DqBaxN4ZF+OFPLJ0Z8I/o0UwQzAOBgNVHQ8BAf8EBAMCAQYw +EgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUDCW1ayuZUKsLp8TXM8UDvqI+ +0YswCgYIKoZIzj0EAwIDRwAwRAIgAJhJ/Fy+nYbVrWExWsVVkUqYeYBKYr8CdbMQ +8p4LXf4CIHrMe8ncC8YT2yAl21getuMOdy8p/1jYfp4moC4L9YBu +-----END CERTIFICATE----- diff --git a/src/create-pets.sh b/src/create-pets.sh new file mode 100644 index 0000000..c745285 --- /dev/null +++ b/src/create-pets.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +export PROPATH=../bin,.,/usr/dlc/tty/netlib/OpenEdge.Net.apl,../config + +rm -f out.log +touch out.log + +# -preloadCLR fails on Linux +mbpro -pf demo.pf clrnetcore -assemblies ../assemblies -q -rr -reusableObjects 500000 -p flusso/demo/createPets.p -param "$1" > out.log + +tail -f out.log diff --git a/src/demo.bat b/src/demo.bat index 29583f9..7f31083 100644 --- a/src/demo.bat +++ b/src/demo.bat @@ -3,4 +3,4 @@ setlocal set PROPATH=.,%DLC%\tty\netlib\OpenEdge.Net.apl -call mbpro -clrnetcore -p flusso\demo\demo.p -basekey "INI" -ininame "win.ini" -param "%1,%2" +call mbpro -pf demo.pf -basekey "INI" -ininame "win.ini" -param "%1,%2" diff --git a/src/demo.pf b/src/demo.pf new file mode 100644 index 0000000..9b0580e --- /dev/null +++ b/src/demo.pf @@ -0,0 +1,6 @@ +-clrnetcore +-assemblies ../assemblies +-q +-rr +-reusableObjects 500000 +-p flusso/demo/demo.p diff --git a/src/demo.sh b/src/demo.sh index 96a4aba..6ff6da6 100755 --- a/src/demo.sh +++ b/src/demo.sh @@ -5,11 +5,8 @@ export PROPATH=../bin,.,/usr/dlc/tty/netlib/OpenEdge.Net.apl,../config rm -f out.log touch out.log -# copy certificates to DLC folder, since -certstorepath can only contain 1 path -cp -p certs/* /usr/dlc/certs - # -preloadCLR fails on Linux -mbpro -clrnetcore -assemblies ../assemblies -q -rr -reusableObjects 500000 -p flusso/demo/demo.p -param "$1,$2" > out.log +mbpro -pf demo.pf -param "$1,$2" > out.log tail -f out.log diff --git a/src/flusso/demo/DemoRunner.cls b/src/flusso/demo/DemoRunner.cls index dd7abdb..5708390 100644 --- a/src/flusso/demo/DemoRunner.cls +++ b/src/flusso/demo/DemoRunner.cls @@ -12,6 +12,7 @@ block-level on error undo, throw. using Progress.Json.ObjectModel.JsonObject. +using Progress.Json.ObjectModel.ObjectModelParser. using Progress.Lang.AppError. using flusso.factory.IConfigurable. using flusso.http.HttpClientFactory. @@ -22,10 +23,11 @@ using flusso.http.IHttpClient. class flusso.demo.DemoRunner implements IConfigurable: - var private char httpClientIdentifier. - var private char requestMethod. - var private char requestUrl. - var private IHttpClient httpClient. + var private char httpClientIdentifier + , requestMethod + , requestUrl. + var private JsonObject requestBody. + var private IHttpClient httpClient. /*------------------------------------------------------------------------------ Purpose: Configure @@ -37,10 +39,31 @@ class flusso.demo.DemoRunner requestMethod = config:GetCharacter("method"). requestUrl = config:GetCharacter("url"). + if config:Has("body") then + requestBody = config:GetJsonObject("body"). + httpClient = HttpClientFactory:Get(httpClientIdentifier). end method. + /*------------------------------------------------------------------------------ + Purpose: Get the IDs out of pet_ids.json + Notes: + ------------------------------------------------------------------------------*/ + method private JsonObject GetPetIds (): + + var ObjectModelParser parser = new ObjectModelParser(). + var JsonObject json. + + file-info:file-name = "pet_ids.json". + if file-info:full-pathname eq ? then return ?. + + json = cast(parser:ParseFile(file-info:full-pathname), JsonObject). + + return json. + + end method. + /*------------------------------------------------------------------------------ Purpose: When the response has a statuscode >= 400, throw as error Notes: @@ -67,7 +90,7 @@ class flusso.demo.DemoRunner when "GET" or when "DELETE" then ExecuteRequests(nrRuns). when "POST" or when "PUT" or when "PATCH" - then ExecuteRequestsWithBody(nrRuns). + then ExecuteRequestsWithBody(nrRuns, requestBody). otherwise undo, throw new AppError(substitute("request method '&1' not implemented", requestMethod), -1). end case. @@ -95,14 +118,8 @@ class flusso.demo.DemoRunner Purpose: Execute a x number of POST/PUT/PATCH requests Notes: ------------------------------------------------------------------------------*/ - method private void ExecuteRequestsWithBody (nrReq as int): - - var JsonObject body = new JsonObject(). - - body:Add("id", 10). - body:Add("userId", 10). - body:Add("title", substitute("&1 at : &2", lc(requestMethod), iso-date(now))). - body:Add("completed", true). + method private void ExecuteRequestsWithBody (nrReq as int + ,body as JsonObject): ExecuteRequests( httpClient , requestUrl @@ -110,7 +127,6 @@ class flusso.demo.DemoRunner , nrReq , body ). - end method. /*------------------------------------------------------------------------------ @@ -123,11 +139,13 @@ class flusso.demo.DemoRunner , nrReq as int , body as JsonObject): - var int i, totalMsec, dryRunMsec, averageMsec. + var int i, totalMsec, dryRunMsec, averageMsec, delCounter. var datetime-tz begin. + var char delUrl = requestUrl. var HttpResponse httpResponse. - var HttpRequestOptions options = new HttpRequestOptions():SetContentType("application/json"). + var HttpRequestOptions options = new HttpRequestOptions():SetContentType("application/json"). + var JsonObject idObj = this-object:GetPetIds(). // dry run etime(true). @@ -136,7 +154,14 @@ class flusso.demo.DemoRunner when "PUT" then httpResponse = httpClient:Put(requestUrl, body, options). when "PATCH" then httpResponse = httpClient:Patch(requestUrl, body, options). when "GET" then httpResponse = httpClient:Get(requestUrl, options). - when "DELETE" then httpResponse = httpClient:Delete(requestUrl, options). + when "DELETE" then do: + if requestUrl matches "*/" then do: + delCounter = idObj:GetInteger("firstId"). + // code for local petstore to delete pets + delUrl = replace(requestUrl, "", string(delCounter)). + end. + httpResponse = httpClient:Delete(delUrl, options). + end. end case. this-object:HandleError(httpResponse). dryRunMsec = etime. @@ -149,22 +174,31 @@ class flusso.demo.DemoRunner do i = 1 to nrReq: etime(true). + case requestMethod: when "POST" then httpResponse = httpClient:Post(requestUrl, body, options). when "PUT" then httpResponse = httpClient:Put(requestUrl, body, options). when "PATCH" then httpResponse = httpClient:Patch(requestUrl, body, options). when "GET" then httpResponse = httpClient:Get(requestUrl, options). - when "DELETE" then httpResponse = httpClient:Delete(requestUrl, options). + when "DELETE" then do: + // code for local petstore to delete pets + if requestUrl matches "*/" then do: + delCounter += 1. + delUrl = replace(requestUrl, "", string(delCounter)). + end. + httpResponse = httpClient:Delete(delUrl, options). + end. end case. + this-object:HandleError(httpResponse). + message substitute("&1 : etime &2 msec - &3 &4 " ,string(i, "999"), etime ,httpResponse:Status - ,httpResponse:Reason) - . + ,httpResponse:Reason). end. - totalMsec = interval(now, begin, "milliseconds"). + totalMsec = interval(now, begin, "milliseconds"). averageMsec = round(totalMsec / nrReq, 3). message substitute("~n Finished &1 &2 requests in &3 seconds (excl. dry)~n", nrReq, requestMethod, round(totalMsec / 1000, 3)) @@ -172,9 +206,14 @@ class flusso.demo.DemoRunner substitute("~n Average : &1 milliseconds", string(averageMsec, "zzz9")) substitute("~n Initialize : &1 milliseconds~n", string(dryRunMsec - averageMsec, "zzz9")) substitute("~n HttpClient : &1", httpClient:GetClass():TypeName) - substitute("~n Request url : &1", requestUrl) + substitute("~n Request url : &1", requestUrl) + (if delCounter gt 10000 then substitute(" (last ID=&1)", delCounter) else "") substitute("~n Request method : &1", requestMethod). + // update pet_ids.json + if requestMethod eq "DELETE" and valid-object(idObj) and requestUrl matches "*/" then do: + idObj:Set("firstId", delCounter + 1). + idObj:WriteFile("pet_ids.json", true). + end. end method. end class. diff --git a/src/flusso/demo/createPets.p b/src/flusso/demo/createPets.p new file mode 100644 index 0000000..f9ec054 --- /dev/null +++ b/src/flusso/demo/createPets.p @@ -0,0 +1,55 @@ +/*------------------------------------------------------------------------ + File : createPets.p + Purpose : + Syntax : + Description : + Author(s) : arno + Created : Wed Oct 15 09:09:50 CEST 2025 + Notes : +----------------------------------------------------------------------*/ + +block-level on error undo, throw. + +using Progress.Json.ObjectModel.JsonObject. +using Progress.Json.ObjectModel.ObjectModelParser. +using flusso.http.HttpClientFactory. +using flusso.http.HttpRequestOptions. +using flusso.http.HttpResponse. +using flusso.http.IHttpClient. + +// default-http-client, abl-http-client, dotnet-http-client +var char clientIdentifier = (if session:parameter gt "" then session:parameter else "default-http-client"). +var char postUrl = "http://petstore:8080/api/pet". +//var char postUrl = "http://10.211.55.2:8080/api/pet". +var longchar singlePetBody = '~{"id":,"category":~{"id":1,"name":"Dogs"},"name":"Demodog-","photoUrls":["www.flusso.nl"],"tags":[~{"id":1,"name":"male"}],"status":"demo"}' + , singlePet. +var int id = 90000, counter. + +var JsonObject jsonPet, idObj = new JsonObject(). +var ObjectModelParser parser = new ObjectModelParser(). +var IHttpClient client = HttpClientFactory:Get(clientIdentifier). +var HttpRequestOptions args = new HttpRequestOptions():SetContentType("application/json") + :SetHeader("Accept", "application/json"). +var HttpResponse response. + +etime(true). +do while true: + singlePet = replace(singlePetBody, "", string(id)). + jsonPet = cast(parser:Parse(singlePet), JsonObject). + response = client:Post(postUrl, jsonPet, args). + message substitute("Created pet with name: Demodog-&1 (status=&2)", id, response:Status). + counter += 1. + if counter ge 750 then + leave. + id += 1. +end. + +message "". +message substitute("[&3] Created &2 pets in &1 seconds!", string(etime / 1000), counter, + client:GetClass():TypeName). +message "". + +// save ids +idObj:Add("firstId", 90000). +idObj:Add("lastId", id). +idObj:WriteFile("pet_ids.json", true). diff --git a/src/flusso/demo/demo.p b/src/flusso/demo/demo.p index 5df0812..44e469b 100644 --- a/src/flusso/demo/demo.p +++ b/src/flusso/demo/demo.p @@ -38,6 +38,7 @@ runner:Run(nrRuns). catch err as Progress.Lang.Error: if err:GetMessage(1) begins "no definition found" then do: + message "~nUsage: demo.sh ~n". message "~nAvailable factory identifiers:". message "------------------------------". cast(factory, Factory):ShowAvailableIdentifiers(). diff --git a/src/flusso/demo/demo_factory.json b/src/flusso/demo/demo_factory.json index 64be817..257bd33 100644 --- a/src/flusso/demo/demo_factory.json +++ b/src/flusso/demo/demo_factory.json @@ -39,7 +39,8 @@ "config" : { "method" : "POST", "httpClientName" : "abl-http-client", - "url" : "http://jsonplaceholder.typicode.com/todos" + "url" : "http://jsonplaceholder.typicode.com/todos", + "body" : {"id":10,"userId":10,"title":"demo-title","completed":true} } }, "demo-abl-https-post" : { @@ -47,7 +48,8 @@ "config" : { "method" : "POST", "httpClientName" : "abl-http-client", - "url" : "https://jsonplaceholder.typicode.com/todos" + "url" : "https://jsonplaceholder.typicode.com/todos", + "body" : {"id":10,"userId":10,"title":"demo-title","completed":true} } }, "demo-dotnet-http-post" : { @@ -55,7 +57,8 @@ "config" : { "method" : "POST", "httpClientName" : "dotnet-http-client", - "url" : "http://jsonplaceholder.typicode.com/todos" + "url" : "http://jsonplaceholder.typicode.com/todos", + "body" : {"id":10,"userId":10,"title":"demo-title","completed":true} } }, "demo-dotnet-https-post" : { @@ -63,7 +66,8 @@ "config" : { "method" : "POST", "httpClientName" : "dotnet-http-client", - "url" : "https://jsonplaceholder.typicode.com/todos" + "url" : "https://jsonplaceholder.typicode.com/todos", + "body" : {"id":10,"userId":10,"title":"demo-title","completed":true} } }, @@ -73,32 +77,40 @@ "config" : { "method" : "PUT", "httpClientName" : "abl-http-client", - "url" : "http://jsonplaceholder.typicode.com/todos/1" - } + "url" : "http://jsonplaceholder.typicode.com/todos/1", + "body" : {"id":10,"userId":10,"title":"demo-title","completed":true} + }, + "disabled" : true }, "demo-abl-https-put" : { "class" : "flusso.demo.DemoRunner", "config" : { "method" : "PUT", "httpClientName" : "abl-http-client", - "url" : "https://jsonplaceholder.typicode.com/todos/1" - } + "url" : "https://jsonplaceholder.typicode.com/todos/1", + "body" : {"id":10,"userId":10,"title":"demo-title","completed":true} + }, + "disabled" : true }, "demo-dotnet-http-put" : { "class" : "flusso.demo.DemoRunner", "config" : { "method" : "PUT", "httpClientName" : "dotnet-http-client", - "url" : "http://jsonplaceholder.typicode.com/todos/1" - } + "url" : "http://jsonplaceholder.typicode.com/todos/1", + "body" : {"id":10,"userId":10,"title":"demo-title","completed":true} + }, + "disabled" : true }, "demo-dotnet-https-put" : { "class" : "flusso.demo.DemoRunner", "config" : { "method" : "PUT", "httpClientName" : "dotnet-http-client", - "url" : "https://jsonplaceholder.typicode.com/todos/1" - } + "url" : "https://jsonplaceholder.typicode.com/todos/1", + "body" : {"id":10,"userId":10,"title":"demo-title","completed":true} + }, + "disabled" : true }, "demo-abl-http-patch" : { @@ -106,32 +118,40 @@ "config" : { "method" : "PATCH", "httpClientName" : "abl-http-client", - "url" : "http://jsonplaceholder.typicode.com/todos/1" - } + "url" : "http://jsonplaceholder.typicode.com/todos/1", + "body" : {"id":10,"userId":10,"title":"demo-title","completed":true} + }, + "disabled" : true }, "demo-abl-https-patch" : { "class" : "flusso.demo.DemoRunner", "config" : { "method" : "PATCH", "httpClientName" : "abl-http-client", - "url" : "https://jsonplaceholder.typicode.com/todos/1" - } + "url" : "https://jsonplaceholder.typicode.com/todos/1", + "body" : {"id":10,"userId":10,"title":"demo-title","completed":true} + }, + "disabled" : true }, "demo-dotnet-http-patch" : { "class" : "flusso.demo.DemoRunner", "config" : { "method" : "PATCH", "httpClientName" : "dotnet-http-client", - "url" : "http://jsonplaceholder.typicode.com/todos/1" - } + "url" : "http://jsonplaceholder.typicode.com/todos/1", + "body" : {"id":10,"userId":10,"title":"demo-title","completed":true} + }, + "disabled" : true }, "demo-dotnet-https-patch" : { "class" : "flusso.demo.DemoRunner", "config" : { "method" : "PATCH", "httpClientName" : "dotnet-http-client", - "url" : "https://jsonplaceholder.typicode.com/todos/1" - } + "url" : "https://jsonplaceholder.typicode.com/todos/1", + "body" : {"id":10,"userId":10,"title":"demo-title","completed":true} + }, + "disabled" : true }, @@ -141,7 +161,8 @@ "method" : "DELETE", "httpClientName" : "abl-http-client", "url" : "http://jsonplaceholder.typicode.com/todos/1" - } + }, + "disabled" : true }, "demo-abl-https-delete" : { "class" : "flusso.demo.DemoRunner", @@ -149,7 +170,8 @@ "method" : "DELETE", "httpClientName" : "abl-http-client", "url" : "https://jsonplaceholder.typicode.com/todos/1" - } + }, + "disabled" : true }, "demo-dotnet-http-delete" : { "class" : "flusso.demo.DemoRunner", @@ -157,7 +179,8 @@ "method" : "DELETE", "httpClientName" : "dotnet-http-client", "url" : "http://jsonplaceholder.typicode.com/todos/1" - } + }, + "disabled" : true }, "demo-dotnet-https-delete" : { "class" : "flusso.demo.DemoRunner", @@ -165,7 +188,8 @@ "method" : "DELETE", "httpClientName" : "dotnet-http-client", "url" : "https://jsonplaceholder.typicode.com/todos/1" - } + }, + "disabled" : true }, @@ -175,7 +199,8 @@ "method" : "GET", "httpClientName" : "abl-http-client", "url" : "http://www.kelters.nl/hulpSetup.zip" - } + }, + "disabled" : true }, "demo-dotnet-http-get-zip" : { "class" : "flusso.demo.DemoRunner", @@ -183,7 +208,8 @@ "method" : "GET", "httpClientName" : "dotnet-http-client", "url" : "http://www.kelters.nl/hulpSetup.zip" - } + }, + "disabled" : true }, "demo-abl-http-get-zip10mb" : { "class" : "flusso.demo.DemoRunner", @@ -191,7 +217,8 @@ "method" : "GET", "httpClientName" : "abl-http-client", "url" : "http://speedtest.tele2.net/10MB.zip" - } + }, + "disabled" : true }, "demo-dotnet-http-get-zip10mb" : { "class" : "flusso.demo.DemoRunner", @@ -199,7 +226,197 @@ "method" : "GET", "httpClientName" : "dotnet-http-client", "url" : "http://speedtest.tele2.net/10MB.zip" + }, + "disabled" : true + }, + + + "local-abl-http-get" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "GET", + "httpClientName" : "abl-http-client", + "url" : "http://petstore:8080/api/pet/findByStatus?status=available" } - } + }, + "local-abl-https-get" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "GET", + "httpClientName" : "abl-http-client", + "url" : "https://caddy:8443/api/pet/findByStatus?status=available" + } + }, + "local-dotnet-http-get" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "GET", + "httpClientName" : "dotnet-http-client", + "url" : "http://petstore:8080/api/pet/findByStatus?status=available" + } + }, + "local-dotnet-https-get" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "GET", + "httpClientName" : "dotnet-http-client", + "url" : "https://caddy:8443/api/pet/findByStatus?status=available" + } + }, + + "local-abl-http-post" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "POST", + "httpClientName" : "abl-http-client", + "url" : "http://petstore:8080/api/pet", + "body" : {"category":{"id":1,"name":"Dogs"},"name":"Demodog","photoUrls":["www.flusso.nl"],"tags":[{"id":1,"name":"male"}],"status":"demo"} + } + }, + "local-abl-https-post" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "POST", + "httpClientName" : "abl-http-client", + "url" : "https://caddy:8443/api/pet", + "body" : {"category":{"id":1,"name":"Dogs"},"name":"Demodog","photoUrls":["www.flusso.nl"],"tags":[{"id":1,"name":"male"}],"status":"demo"} + } + }, + "local-dotnet-http-post" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "POST", + "httpClientName" : "dotnet-http-client", + "url" : "http://petstore:8080/api/pet", + "body" : {"category":{"id":1,"name":"Dogs"},"name":"Demodog","photoUrls":["www.flusso.nl"],"tags":[{"id":1,"name":"male"}],"status":"demo"} + } + }, + "local-dotnet-https-post" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "POST", + "httpClientName" : "dotnet-http-client", + "url" : "https://caddy:8443/api/pet", + "body" : {"category":{"id":1,"name":"Dogs"},"name":"Demodog","photoUrls":["www.flusso.nl"],"tags":[{"id":1,"name":"male"}],"status":"demo"} + } + }, + + + "local-abl-http-put" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "PUT", + "httpClientName" : "abl-http-client", + "url" : "http://petstore:8080/api/pet", + "body" : {"id":1,"category":{"id":1,"name":"Dogs"},"name":"Demodog","photoUrls":["www.flusso.nl"],"tags":[{"id":1,"name":"male"}],"status":"demo"} + }, + "disabled" : true + }, + "local-abl-https-put" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "PUT", + "httpClientName" : "abl-http-client", + "url" : "https://caddy:8443/api/pet", + "body" : {"id":1,"category":{"id":1,"name":"Dogs"},"name":"Demodog","photoUrls":["www.flusso.nl"],"tags":[{"id":1,"name":"male"}],"status":"demo"} + }, + "disabled" : true + }, + "local-dotnet-http-put" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "PUT", + "httpClientName" : "dotnet-http-client", + "url" : "http://petstore:8080/api/pet", + "body" : {"id":1,"category":{"id":1,"name":"Dogs"},"name":"Demodog","photoUrls":["www.flusso.nl"],"tags":[{"id":1,"name":"male"}],"status":"demo"} + }, + "disabled" : true + }, + "local-dotnet-https-put" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "PUT", + "httpClientName" : "dotnet-http-client", + "url" : "https://caddy:8443/api/pet", + "body" : {"id":1,"category":{"id":1,"name":"Dogs"},"name":"Demodog","photoUrls":["www.flusso.nl"],"tags":[{"id":1,"name":"male"}],"status":"demo"} + }, + "disabled" : true + }, + + + "local-abl-http-patch" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "PATCH", + "httpClientName" : "abl-http-client", + "url" : "http://petstore:8080/api/pet", + "body" : {"id":1,"category":{"id":1,"name":"Dogs"},"name":"Demodog","photoUrls":["www.flusso.nl"],"tags":[{"id":1,"name":"male"}],"status":"demo"} + }, + "disabled" : true + }, + "local-abl-https-patch" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "PATCH", + "httpClientName" : "abl-http-client", + "url" : "https://caddy:8443/api/pet", + "body" : {"id":1,"category":{"id":1,"name":"Dogs"},"name":"Demodog","photoUrls":["www.flusso.nl"],"tags":[{"id":1,"name":"male"}],"status":"demo"} + }, + "disabled" : true + }, + "local-dotnet-http-patch" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "PATCH", + "httpClientName" : "dotnet-http-client", + "url" : "http://petstore:8080/api/pet", + "body" : {"id":1,"category":{"id":1,"name":"Dogs"},"name":"Demodog","photoUrls":["www.flusso.nl"],"tags":[{"id":1,"name":"male"}],"status":"demo"} + }, + "disabled" : true + }, + "local-dotnet-https-patch" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "PATCH", + "httpClientName" : "dotnet-http-client", + "url" : "https://caddy:8443/api/pet", + "body" : {"id":1,"category":{"id":1,"name":"Dogs"},"name":"Demodog","photoUrls":["www.flusso.nl"],"tags":[{"id":1,"name":"male"}],"status":"demo"} + }, + "disabled" : true + }, + + + "local-abl-http-delete" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "DELETE", + "httpClientName" : "abl-http-client", + "url" : "http://petstore:8080/api/pet/" + } + }, + "local-abl-https-delete" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "DELETE", + "httpClientName" : "abl-http-client", + "url" : "https://caddy:8443/api/pet/" + } + }, + "local-dotnet-http-delete" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "DELETE", + "httpClientName" : "dotnet-http-client", + "url" : "http://petstore:8080/api/pet/" + } + }, + "local-dotnet-https-delete" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "DELETE", + "httpClientName" : "dotnet-http-client", + "url" : "https://caddy:8443/api/pet/" + } + } } diff --git a/src/flusso/demo/demo_factory_vm.json b/src/flusso/demo/demo_factory_vm.json new file mode 100644 index 0000000..80b9306 --- /dev/null +++ b/src/flusso/demo/demo_factory_vm.json @@ -0,0 +1,34 @@ +{ + "local-abl-http-get-vm" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "GET", + "httpClientName" : "abl-http-client", + "url" : "http://10.211.55.2:8080/api/pet/findByStatus?status=available" + } + }, + "local-abl-https-get-vm" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "GET", + "httpClientName" : "abl-http-client", + "url" : "https://caddy.lan:8443/api/pet/findByStatus?status=available" + } + }, + "local-dotnet-http-get-vm" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "GET", + "httpClientName" : "dotnet-http-client", + "url" : "http://10.211.55.2:8080/api/pet/findByStatus?status=available" + } + }, + "local-dotnet-https-get-vm" : { + "class" : "flusso.demo.DemoRunner", + "config" : { + "method" : "GET", + "httpClientName" : "dotnet-http-client", + "url" : "https://caddy.lan:8443/api/pet/findByStatus?status=available" + } + } +} diff --git a/src/flusso/factory/Factory.cls b/src/flusso/factory/Factory.cls index 648d2e1..541ec81 100644 --- a/src/flusso/factory/Factory.cls +++ b/src/flusso/factory/Factory.cls @@ -19,16 +19,17 @@ using Progress.Lang.AppError. class flusso.factory.Factory implements IFactory: - define private temp-table ttdefintion no-undo + define private temp-table ttdefinition no-undo field identifier as char - field classname as char - field singleton as logical - field config as Progress.Lang.Object + field classname as char + field singleton as logical + field config as Progress.Lang.Object + field disabled as logical . define private temp-table ttinstance no-undo field objtype as char - field obj as Progress.Lang.Object + field obj as Progress.Lang.Object . /*------------------------------------------------------------------------------ @@ -59,12 +60,12 @@ class flusso.factory.Factory implements IFactory: var Progress.Lang.Object instanceRef. var logical singleInstanceFound. - find ttdefintion where ttdefintion.identifier = instanceName no-error. - if not available(ttdefintion) then + find ttdefinition where ttdefinition.identifier = instanceName no-error. + if not available(ttdefinition) then undo, throw new AppError(substitute("no definition found for '&1'", instanceName), -1). singleInstanceFound = false. - if ttdefintion.singleton then do: + if ttdefinition.singleton then do: for first ttinstance where ttinstance.objtype = instanceName: instanceRef = ttinstance.obj. singleInstanceFound = true. @@ -72,21 +73,23 @@ class flusso.factory.Factory implements IFactory: end. if not valid-object(instanceRef) then do: - instanceRef = dynamic-new(ttdefintion.classname)() no-error. + if ttdefinition.disabled then + undo, throw new AppError(substitute("class '&1' is disabled", ttdefinition.classname), -1). + instanceRef = dynamic-new(ttdefinition.classname)() no-error. if not error-status:error and type-of(instanceRef, IConfigurable) - and valid-object(ttdefintion.config) then - cast(instanceRef, IConfigurable):Configure(cast(ttdefintion.config, JsonObject)). + and valid-object(ttdefinition.config) then + cast(instanceRef, IConfigurable):Configure(cast(ttdefinition.config, JsonObject)). end. if not valid-object(instanceRef) then - undo, throw new AppError(substitute("class not found: &1", ttdefintion.classname), -1). + undo, throw new AppError(substitute("class not found: &1", ttdefinition.classname), -1). - if ttdefintion.singleton and not singleInstanceFound then do: + if ttdefinition.singleton and not singleInstanceFound then do: create ttinstance. assign - ttinstance.objtype = ttdefintion.identifier + ttinstance.objtype = ttdefinition.identifier ttinstance.obj = instanceRef . end. @@ -110,7 +113,7 @@ class flusso.factory.Factory implements IFactory: @param instanceName The logical name of the requested object to delete ------------------------------------------------------------------------------*/ method public logical Has(instanceName as char): - return can-find(first ttdefintion where ttdefintion.identifier = instanceName). + return can-find(first ttdefinition where ttdefinition.identifier = instanceName). end method. // Has /*------------------------------------------------------------------------------ @@ -155,7 +158,7 @@ class flusso.factory.Factory implements IFactory: var char[] names. var JsonObject defintionObject, config. var char identifier, classname. - var logical singleton. + var logical singleton, disabled. names = jsonDefintions:GetNames(). do i = 1 to extent(names): @@ -173,7 +176,8 @@ class flusso.factory.Factory implements IFactory: when 4 then do: defintionObject = jsonDefintions:GetJsonObject(identifier). classname = defintionObject:GetCharacter("class"). - singleton = (defintionObject:Has("singleton") and defintionObject:GetLogical("singleton")). + singleton = (defintionObject:Has("singleton") and defintionObject:GetLogical("singleton")). + disabled = (defintionObject:Has("disabled") and defintionObject:GetLogical("disabled")). if defintionObject:Has("config") then config = defintionObject:GetJsonObject("config"). end. @@ -184,7 +188,7 @@ class flusso.factory.Factory implements IFactory: end case. - AddDefinition(identifier, classname, singleton, config). + AddDefinition(identifier, classname, singleton, config, disabled). end. // do i = 1 to extent(names)... @@ -198,17 +202,18 @@ class flusso.factory.Factory implements IFactory: @param singleton @param config ------------------------------------------------------------------------------*/ - method private void AddDefinition(identifier as char, classname as char, singleton as logical, config as JsonObject): + method private void AddDefinition(identifier as char, classname as char, singleton as logical, config as JsonObject, disabled as logical): - find ttdefintion where ttdefintion.identifier = identifier no-error. - if not available(ttdefintion) then do: - create ttdefintion. - ttdefintion.identifier = identifier. + find ttdefinition where ttdefinition.identifier = identifier no-error. + if not available(ttdefinition) then do: + create ttdefinition. + ttdefinition.identifier = identifier. end. - ttdefintion.classname = classname. - ttdefintion.singleton = singleton. - ttdefintion.config = config. + ttdefinition.classname = classname. + ttdefinition.singleton = singleton. + ttdefinition.disabled = disabled. + ttdefinition.config = config. end method. @@ -218,8 +223,8 @@ class flusso.factory.Factory implements IFactory: ------------------------------------------------------------------------------*/ method public void ShowAvailableIdentifiers (): - for each ttdefintion /*by ttdefintion.identifier*/: - message ttdefintion.identifier. + for each ttdefinition where ttdefinition.disabled eq false /*by ttdefinition.identifier*/: + message ttdefinition.identifier. end. end method. diff --git a/src/flusso/http/AblHttpClient.cls b/src/flusso/http/AblHttpClient.cls index 003e336..e1e181c 100644 --- a/src/flusso/http/AblHttpClient.cls +++ b/src/flusso/http/AblHttpClient.cls @@ -27,7 +27,6 @@ using flusso.http.IHttpClient. class flusso.http.AblHttpClient implements IHttpClient: - //var private IHttpClientLibrary httpClientLibrary. var private ClientLibraryBuilder libraryBuilder. constructor public AblHttpClient(): @@ -116,16 +115,22 @@ class flusso.http.AblHttpClient implements IHttpClient: response:Headers = GetHeaders(oeResponse). // tlserr has more properties, inner error and methods - catch tlserr as TlsClientAuthenticationError : + catch tlserr as TlsClientAuthenticationError: response:Status = 525. // SSL Handshake Failed response:Reason = tlserr:GetShortMessage(). //if valid-object(tlserr:InnerError) then // response:Reason += " / " + tlserr:InnerError:GetMessage(1). end catch. - catch err1 as Progress.Lang.Error: - response:Status = int(err1:GetMessage(1)). - response:Reason = substitute("&1:&2 - &3", err1:GetMessage(2), err1:GetMessage(3), err1:GetMessage(4)). + catch err as Progress.Lang.Error: + define variable errcode as int no-undo initial ?. + errcode = int(err:GetMessage(1)) no-error. + if errcode eq ? then + assign response:Status = 500 + response:Reason = err:GetMessage(1). + else + assign response:Status = errcode + response:Reason = substitute("&1:&2 - &3", err:GetMessage(2), err:GetMessage(3), err:GetMessage(4)). end catch. end. // do on error...