├── docs ├── docker.md ├── security.md ├── sessions.md ├── release-notes.md ├── img │ ├── sample.png │ ├── favicon-32.png │ └── dyalog-white.svg ├── assets │ └── apl385.ttf ├── LICENSE.md ├── settings-shared.md ├── settings-overview.md ├── settings-container.md ├── settings-rest.md ├── css │ └── main.css ├── using-request.md ├── json.md ├── settings-session.md ├── settings-json.md ├── methods-shared.md ├── settings-cors.md ├── request.md ├── rest.md ├── settings-hooks.md ├── methods-instance.md ├── using.md ├── index.md ├── conga.md ├── concepts.md ├── settings-conga.md └── settings-operational.md ├── .gitignore ├── Samples ├── REST │ ├── Put.aplf │ ├── Post.aplf │ ├── Delete.aplf │ ├── lookup.aplf │ ├── Database.dcf │ ├── InitSession.aplf │ ├── Authenticate.aplf │ ├── config.json │ ├── Test.aplf │ ├── Initialize.aplf │ ├── TableToNS.aplf │ ├── Get.aplf │ ├── GetProducts.aplf │ ├── GetOrders.aplf │ └── GetCustomers.aplf ├── JSON │ ├── Utils │ │ └── Reverse.dyalog │ ├── GetSignObject.dyalog │ ├── GetSign.dyalog │ └── GetSignWithRequest.dyalog └── JSON_Namespace │ └── test.dyalog ├── Tests ├── mixed │ ├── reverse.dyalog │ ├── Excluded.dyalog │ ├── loans.dyalog │ ├── loansclass.dyalog │ └── demo.txt ├── login │ ├── loginneeded.dyalog │ ├── payloadcreds.dyalog │ ├── nologinneeded.dyalog │ ├── jarvisconfig.json │ └── Authenticate.dyalog ├── testconfig.json ├── teardown.dyalog ├── AllowGETs │ └── jarvisconfig.json ├── sessions │ ├── InitializeSession.dyalog │ ├── Add.dyalog │ ├── Subtract.dyalog │ └── jarvisconfig.json ├── unit.dyalogtest ├── setup.dyalog ├── test_KillOnDisconnect.dyalog ├── Secure │ ├── PickCert.dyalog │ └── TestSecure.dyalog ├── test_sessions.dyalog ├── run │ └── testClass.dyalog ├── test_PostProcess.dyalog └── ContentTypes │ └── test_contentTypes.aplf ├── Distribution ├── Jarvis.dws └── JarvisService.dws ├── Dockerfile ├── Dockerfile.20.0 ├── Service ├── Config.dyalog ├── SysLog.dyalog └── JarvisService.dyalog ├── Source ├── Run.aplf ├── Updates.dyalog └── AutoStart.dyalog ├── Demos ├── Client.demo └── Server.demo ├── Jarvis.dyalogbuild ├── JarvisService.dyalogbuild ├── README.md ├── Jarvis.demo ├── CI └── CI.md ├── apl-package.json ├── LICENSE ├── Docker ├── entrypoint └── README.md ├── Jenkinsfile └── mkdocs.yml /docs/docker.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/security.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/sessions.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | site/ 2 | -------------------------------------------------------------------------------- /docs/release-notes.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Samples/REST/Put.aplf: -------------------------------------------------------------------------------- 1 | r←Put req 2 | ∘∘∘ 3 | -------------------------------------------------------------------------------- /Samples/REST/Post.aplf: -------------------------------------------------------------------------------- 1 | r←Post ns 2 | ∘∘∘ 3 | -------------------------------------------------------------------------------- /Samples/REST/Delete.aplf: -------------------------------------------------------------------------------- 1 | r←Delete ns 2 | ∘∘∘ 3 | 4 | -------------------------------------------------------------------------------- /Tests/mixed/reverse.dyalog: -------------------------------------------------------------------------------- 1 | r←reverse data 2 | r←⌽data 3 | -------------------------------------------------------------------------------- /Samples/JSON/Utils/Reverse.dyalog: -------------------------------------------------------------------------------- 1 | r←Reverse data 2 | r←⌽data 3 | -------------------------------------------------------------------------------- /Tests/login/loginneeded.dyalog: -------------------------------------------------------------------------------- 1 | r←loginneeded arg 2 | r←(⊃⎕SI)arg 3 | -------------------------------------------------------------------------------- /Tests/login/payloadcreds.dyalog: -------------------------------------------------------------------------------- 1 | r←payloadcreds arg 2 | r←(⊃⎕SI)arg 3 | -------------------------------------------------------------------------------- /Tests/login/nologinneeded.dyalog: -------------------------------------------------------------------------------- 1 | r←nologinneeded arg 2 | r←(⊃⎕SI)arg 3 | -------------------------------------------------------------------------------- /Tests/testconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "Paradigm" : "JSON", 3 | "Port" : 9000 4 | } 5 | -------------------------------------------------------------------------------- /docs/img/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/Jarvis/HEAD/docs/img/sample.png -------------------------------------------------------------------------------- /Samples/REST/lookup.aplf: -------------------------------------------------------------------------------- 1 | lookup←{ 2 | {⍵↑⍨1⌈≢⍵}1+⍸,(⍕¨1↓[1]⍪⍺)(≡⍤(1⌊¯1+≢⍵))⍵ 3 | } 4 | -------------------------------------------------------------------------------- /Tests/teardown.dyalog: -------------------------------------------------------------------------------- 1 | r←teardown dummy 2 | r←'' 3 | {}#.⎕EX'JSONServer' 'HttpCommand' 4 | -------------------------------------------------------------------------------- /docs/assets/apl385.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/Jarvis/HEAD/docs/assets/apl385.ttf -------------------------------------------------------------------------------- /Distribution/Jarvis.dws: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/Jarvis/HEAD/Distribution/Jarvis.dws -------------------------------------------------------------------------------- /Tests/login/jarvisconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "AuthenticateFn" : "Authenticate", 3 | "Port" : 22224 4 | } -------------------------------------------------------------------------------- /docs/img/favicon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/Jarvis/HEAD/docs/img/favicon-32.png -------------------------------------------------------------------------------- /Samples/REST/Database.dcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/Jarvis/HEAD/Samples/REST/Database.dcf -------------------------------------------------------------------------------- /Distribution/JarvisService.dws: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/Jarvis/HEAD/Distribution/JarvisService.dws -------------------------------------------------------------------------------- /Tests/AllowGETs/jarvisconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "AllowGETs" : 1, 3 | "Port" : 22224, 4 | "HTMLInterface" : 0 5 | } -------------------------------------------------------------------------------- /Tests/sessions/InitializeSession.dyalog: -------------------------------------------------------------------------------- 1 | InitializeSession req 2 | ⍝ initializes the session 3 | req.Session.Sum←0 4 | -------------------------------------------------------------------------------- /Tests/sessions/Add.dyalog: -------------------------------------------------------------------------------- 1 | r←req Add arg 2 | ⍝ arg is an integer array 3 | req.Session.Sum+←+/arg 4 | r←req.Session.Sum 5 | -------------------------------------------------------------------------------- /Samples/REST/InitSession.aplf: -------------------------------------------------------------------------------- 1 | InitSession req 2 | ⍝ Initialize the user's session 3 | req.Session.Role←req.Role ⍝ set the role for the user 4 | -------------------------------------------------------------------------------- /Tests/sessions/Subtract.dyalog: -------------------------------------------------------------------------------- 1 | ∇ r←req Subtract arg 2 | ⍝ arg is an integer array 3 | req.Session.Sum-←+/arg 4 | r←req.Session.Sum 5 | ∇ 6 | -------------------------------------------------------------------------------- /Tests/sessions/jarvisconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "SessionInitFn" : "InitializeSession", 3 | "SessionTimeout" : .25, 4 | "Port" : 22224, 5 | "SessionUseCookie" : 1 6 | } -------------------------------------------------------------------------------- /Samples/JSON/GetSignObject.dyalog: -------------------------------------------------------------------------------- 1 | ns←GetSignObject ns 2 | ⍝ Return a sign object contain month, day (provided as input) and sign 3 | 4 | ns.sign←GetSign ns.(month day) 5 | -------------------------------------------------------------------------------- /Tests/unit.dyalogtest: -------------------------------------------------------------------------------- 1 | DyalogTest : 0.1 2 | ID : JSONServer_Basic_Unit 3 | Description: Basic unit tests for JSONServer.dyalog 4 | 5 | Setup : setup 6 | Teardown: teardown -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM dyalog/dyalog:19.0 2 | USER root 3 | 4 | ADD . /opt/mdyalog/Jarvis 5 | 6 | EXPOSE 8080 7 | ENV VERSION=19.0 8 | ADD Docker/entrypoint /entrypoint 9 | USER dyalog 10 | -------------------------------------------------------------------------------- /Dockerfile.20.0: -------------------------------------------------------------------------------- 1 | FROM dyalog/dyalog:20.0 2 | USER root 3 | 4 | ADD . /opt/mdyalog/Jarvis 5 | 6 | EXPOSE 8080 7 | ENV VERSION=20.0 8 | ADD Docker/entrypoint /entrypoint 9 | USER dyalog 10 | -------------------------------------------------------------------------------- /Tests/mixed/Excluded.dyalog: -------------------------------------------------------------------------------- 1 | r←Excluded data 2 | ⍝ this demo function should not be able to be called with ExcludeFns set to '[A-Z].*' 3 | r←'This function would be excluded if ExcludeFns had [A-Z].*' 4 | -------------------------------------------------------------------------------- /Service/Config.dyalog: -------------------------------------------------------------------------------- 1 | :Namespace Config 2 | Port←443 3 | Secure←1 4 | ServerCertSKI←'b4a60e4dccdcc16a25f8babc6184fa0258d963b5' 5 | Priority←'SECURE128:+SECURE192:-VERS-ALL:+VERS-TLS1.2:+VERS-TLS1.1' 6 | 7 | :EndNamespace 8 | -------------------------------------------------------------------------------- /Samples/REST/Authenticate.aplf: -------------------------------------------------------------------------------- 1 | r←Authenticate req;ind 2 | ⍝ simple authentication 3 | →r←0 4 | →0⍴⍨r←(≢Database.Users)/threads)/ 22 | -------------------------------------------------------------------------------- /Jarvis.demo: -------------------------------------------------------------------------------- 1 | )clear 2 | ]load /git/Jarvis/Source/Jarvis 3 | srv←⎕NEW Jarvis 4 | srv.CodeLocation←'/git/Jarvis/Samples/JSON/' 5 | srv.Port←8080 6 | srv.Start 7 | 8 | ⍝ Now make a call to it: 9 | ]load HTTPCommand 10 | cmd←⎕NEW HttpCommand 11 | cmd.(Command URL)←'POST' 'localhost:8080/GetSign' 12 | cmd.Headers⍪←'content-type' 'application/json' 13 | cmd.Params←'[10,31]' 14 | q←cmd.Run 15 | q.rc 16 | q.Data 17 | 18 | cmd.Params←'[2,23]' 19 | q←cmd.Run 20 | q.rc 21 | q.HttpStatus 22 | q.HttpMessage 23 | 24 | q←HttpCommand.GetJSON 'post' 'localhost:8080/GetSign' (10 31) 25 | q.rc 26 | q.Data -------------------------------------------------------------------------------- /Tests/Secure/PickCert.dyalog: -------------------------------------------------------------------------------- 1 | r←PickCert store;certs 2 | r←⍬ 3 | :If 0∊⍴store ⋄ store←'My' ⋄ :EndIf 4 | :If 'W'=⊃3⊃#.⎕WG'APLVersion' 5 | :If ~0∊⍴certs←#.DRC.X509Cert.ReadCertFromStore'My' 6 | ⎕←'Select a certificate:' 7 | ⎕←(⍳≢certs),⍪certs.Formatted.Subject 8 | :Trap 0 9 | r←⎕⊃certs 10 | :Else 11 | ⎕←'No certificate selected' 12 | :EndTrap 13 | :Else 14 | ⎕←'No certificates in your Windows certificate store' 15 | :EndIf 16 | :Else 17 | ⎕←'This can run on Windows only.' 18 | :EndIf 19 | -------------------------------------------------------------------------------- /CI/CI.md: -------------------------------------------------------------------------------- 1 | ## Jarvis Continuous Integration 2 | This folder will contain files related to the building and testing of draft releases of Jarvis. 3 | 4 | ### Updating the Docker container to use a new Dyalog APL version 5 | 1. Ensure there's a public Docker container for the Dyalog version.
[Check the tags for the dyalog/dyalog container.](https://hub.docker.com/r/dyalog/dyalog/tags) 6 | 2. Modify the FROM statement in the Dockerfile file to use the new Dyalog version 7 | 3. Modify the "export DYALOG" and "export WSPATH" statements in the entrypoint file in the Docker folder to use the new Dyalog version -------------------------------------------------------------------------------- /apl-package.json: -------------------------------------------------------------------------------- 1 | { 2 | api: "Jarvis", 3 | assets: "", 4 | description: "JSON and REST Web Service Framework", 5 | documentation: "https://dyalog.github.io/Jarvis", 6 | files: "", 7 | group: "dyalog", 8 | io: 1, 9 | license: "MIT", 10 | lx: "", 11 | maintainer: "", 12 | minimumAplVersion: "18.0", 13 | ml: 1, 14 | name: "Jarvis", 15 | os_lin: 1, 16 | os_mac: 1, 17 | os_win: 1, 18 | project_url: "https://github.com/Dyalog/Jarvis", 19 | source: "source/Jarvis.dyalog", 20 | tags: "mac-os,windows,linux,http,webservice,json,rest", 21 | userCommandScript: "", 22 | version: "1.21.2", 23 | } 24 | -------------------------------------------------------------------------------- /Demos/Server.demo: -------------------------------------------------------------------------------- 1 | ⍝ NB requires v16.0 or later 2 | ⍝ NB to run this demo: 3 | ⍝ Replace [Jarvis] in the lines below with the folder where you have downloaded or cloned the Jarvis repository 4 | )clear 5 | ⎕pw←1000 6 | )ns Zodiac 7 | ]load [Jarvis]/Samples/JSON/* -target=Zodiac 8 | ⎕VR 'Zodiac.GetSign' 9 | Zodiac.GetSign 10 31 10 | 11 | (halloween←⎕NS '').(month day)←10 31 12 | hweensign←Zodiac.GetSignObject halloween 13 | hweensign.(month day sign) 14 | ⎕JSON hweensign 15 | 16 | ]load [Jarvis]/Source/Jarvis 17 | srv←⎕NEW Jarvis 18 | srv.CodeLocation←#.Zodiac 19 | srv.Port←8080 20 | srv.Start 21 | ⍝ Now run the client demo 22 | srv.Stop 23 | 24 | )clear 25 | ]load [Jarvis]/Source/Jarvis 26 | ⎕←(srv rc)←Jarvis.Run (8080 '/devt/Jarvis/Samples/JSON') 27 | 28 | ⍝ dyalog [Jarvis]/Distribution/Jarvis.dws -Port=8080 -CodeLocation="[Jarvis]/Sample" 29 | -------------------------------------------------------------------------------- /Tests/test_sessions.dyalog: -------------------------------------------------------------------------------- 1 | (rc msg)←test_sessions;l;path 2 | :If '⍝∇⍣§'≡4↑l←⊃⊢/⎕NR'test_sessions' 3 | path←⊃1 ⎕NPARTS 2⊃'§'(≠⊆⊢)l 4 | ⎕SE.SALT.Load path,'../Source/DServer.dyalog' 5 | ⎕SE.SALT.Load'HttpCommand' 6 | HttpCommand.Upgrade 7 | ds←⎕NEW DServer 8 | ds.CodeLocation←path,'sessions/' 9 | ds.SessionInitFn←'InitializeSession' 10 | ds.AuthenticateFn←'Login' 11 | ds.Paradigm←'REST' 12 | ds.Debug←2 13 | ds.(SessionTimeout SessionPollingTime SessionCleanupTime)←30 0.5 5 14 | :If 0=⊃(rc msg)←ds.Start 15 | c←⎕NEW HttpCommand 16 | c.URL←'localhost:8080/Login' 17 | 'content-type'c.SetHeader'application/json' 18 | c.Params←{t←⎕NS'' ⋄ t⊣t.(UserID Password)←⍵}'user' 'password' 19 | c.WaitTime←600 20 | c.Command←'post' 21 | r←c.Run 22 | ∘∘∘ 23 | :EndIf 24 | 25 | :Else 26 | (rc msg)←¯1 'No SALT source file information found!' 27 | :EndIf 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Dyalog 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Samples/REST/GetProducts.aplf: -------------------------------------------------------------------------------- 1 | r←req GetProducts parts;oind;dind;pind;ExitIf 2 | ⍝ /Products[/productid[/orders]] 3 | ExitIf←→⍴∘0 4 | r←'' 5 | :Select ≢parts 6 | 7 | :Case 0 ⍝ GET products - retrieve list of products 8 | 9 | ExitIf req.Fail 404×1=≢Database.Products 10 | r←TableToNS Database.Products 11 | r.itemUri←req.MakeURI¨1↓Database.Products[;1] 12 | 13 | :Case 1 ⍝ GET products/productid - retrieve information for a product 14 | 15 | ExitIf req.Fail 404×0=pind←Database.Products[;1]lookup parts[1] 16 | r←⊃TableToNS Database.Products[1,pind;] 17 | 18 | :Case 2 ⍝ GET products/productid/orders - retrieve orders for a product 19 | 20 | ExitIf req.Fail 404×0∊dind←Database.Details[;2]lookup parts[1] 21 | r←TableToNS Database.Details[1,dind;] 22 | r.itemUri←req.MakeURI¨Database.Details[oind;2] 23 | 24 | :Case 3 ⍝ GET orders/orderid/items/itemid 25 | 26 | ExitIf req.Fail 404×0∊Database.Details[;1 2]lookup parts[1 3] 27 | ExitIf req.Fail 404×0∊pind←Database.Products[;1]lookup parts[3] 28 | r←TableToNS Database.Products[1,pind;] 29 | 30 | :Else 31 | req.Fail 404 32 | :EndSelect 33 | -------------------------------------------------------------------------------- /docs/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Dyalog 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Samples/REST/GetOrders.aplf: -------------------------------------------------------------------------------- 1 | r←req GetOrders parts;oind;dind;pind;ExitIf 2 | ⍝ /orders[/orderid[/items[/itemid]]] 3 | ExitIf←→⍴∘0 4 | r←'' 5 | :Select ≢parts 6 | 7 | :Case 0 ⍝ GET orders - retrieve list of orders 8 | 9 | ExitIf req.Fail 404×1=≢Database.Orders 10 | r←TableToNS Database.Orders 11 | r.orderUri←req.MakeURI¨1↓Database.Orders[;1] 12 | 13 | :Case 1 ⍝ GET orders/orderid - retrieve information for an order 14 | 15 | ExitIf req.Fail 404×0=oind←Database.Orders[;1]lookup parts[1] 16 | r←⊃TableToNS Database.Orders[1,oind;] 17 | r.customerUri←'Customers'req.MakeURI Database.Orders[oind;2] 18 | 19 | :Case 2 ⍝ GET orders/orderid/items 20 | 21 | ExitIf req.Fail 404×0∊dind←Database.Details[;1]lookup parts[1] 22 | r←TableToNS Database.Details[1,dind;] 23 | r.itemUri←req.MakeURI¨Database.Details[oind;2] 24 | 25 | :Case 3 ⍝ GET orders/orderid/items/itemid 26 | 27 | ExitIf req.Fail 404×0∊Database.Details[;1 2]lookup parts[1 3] 28 | ExitIf req.Fail 404×0∊pind←Database.Products[;1]lookup parts[3] 29 | r←TableToNS Database.Products[1,pind;] 30 | 31 | :Else 32 | req.Fail 404 33 | :EndSelect 34 | -------------------------------------------------------------------------------- /Tests/Secure/TestSecure.dyalog: -------------------------------------------------------------------------------- 1 | TestSecure pathToJSONServer 2 | :If 0=⎕NC'JSONServer' 3 | ⎕SE.SALT.Load pathToJSONServer,'/Source/JSONServer.dyalog' 4 | :EndIf 5 | ⎕SE.SALT.Load pathToJSONServer,'/Sample/GetSign*.dyalog' 6 | js←⎕NEW JSONServer 7 | dyalog←2 ⎕NQ'.' 'GetEnvironment' 'Dyalog' 8 | js.Secure←1 9 | js.SSLValidation←64 ⍝ request, but do not require a certificate 10 | js.RootCertDir←dyalog,'\TestCertificates\Ca\' 11 | js.ServerCertFile←dyalog,'\TestCertificates\Server\localhost-cert.pem' 12 | js.ServerKeyFile←dyalog,'\TestCertificates\Server\localhost-key.pem' 13 | ⎕FX↑'r←ValidateRequest req' 'r←0' ':if ~0∊⍴req.PeerCert ⋄ ''Subject'' ''Valid From'' ''Valid To'',⍪⊃req.PeerCert.Formatted.(Subject ValidFrom ValidTo) ⋄ :EndIf ' 14 | js.Start 15 | ⎕←(⎕UCS 13),'⍝ To test using HttpCommand:' 16 | ⎕←'⍝ Make sure you have HttpCommand.Version 2.1.17 or later.' 17 | ⎕←' d←2 ⎕NQ ''.'' ''GetEnvironment'' ''dyalog''' 18 | ⎕←' key←d,''/TestCertificates/client/John Doe-key.pem''' 19 | ⎕←' cert←d,''/TestCertificates/client/John Doe-cert.pem''' 20 | ⎕←' r←HttpCommand.GetJSON''post'' ''localhost:8080/GetSign''(2,23)''''(cert key)' 21 | -------------------------------------------------------------------------------- /docs/settings-shared.md: -------------------------------------------------------------------------------- 1 | Shared settings are shared between all instances of `Jarvis` in the workspace. 2 | 3 | ### `LDRC` 4 | |--|--| 5 | |Description|`LDRC` is a reference to the initialized Conga library. It can be used to access Conga functions. See the [Conga User Guide](https://docs.dyalog.com/latest/Conga%20User%20Guide.pdf) for more information on Conga's functions.| 6 | |Default|`LDRC` is initially `''` and is set by `Jarvis` to the Conga library reference upon initialization.| 7 | |Examples|`j.LDRC.Names '.'`| 8 | |Notes|You should not attempt to set `LDRC` yourself.| 9 | 10 | ### `CongaPath` 11 | |--|--| 12 | |Description|`CongaPath` is a pathname to the folder pathname used to tell `Jarvis` where to look for either the Conga workspace or the Conga shared libraries.| 13 | |Default|`''`| 14 | |Examples|`Jarvis.CongaPath←'c:\myapp\Conga\'`| 15 | |Notes|See [Jarvis and Conga](./conga.md) for more information.| 16 | 17 | ### `CongaRef` 18 | |--|--| 19 | |Description|`CongaRef` is a name of or reference to the `Conga` namespace in workspace.| 20 | |Default|`''`| 21 | |Examples|`Jarvis.CongaRef←#.MyApp.Conga`| 22 | |Notes|See [Jarvis and Conga](./conga.md) for more information| 23 | -------------------------------------------------------------------------------- /Tests/run/testClass.dyalog: -------------------------------------------------------------------------------- 1 | :Class testClass 2 | 3 | :Field public field1 4 | :Field _prop1←'prop1 value' 5 | 6 | :Property prop1 7 | :Access public 8 | ∇ r←Get 9 | r←_prop1 10 | ∇ 11 | 12 | ∇ Set arg 13 | _prop1←arg.NewValue 14 | ∇ 15 | :EndProperty 16 | 17 | ∇ make 18 | :Access public 19 | :Implements constructor 20 | field1←'default' 21 | ∇ 22 | 23 | ∇ make1 arg 24 | :Access public 25 | :Implements constructor 26 | field1←arg 27 | ∇ 28 | 29 | ∇ niladic 30 | :Access public 31 | ∇ 32 | 33 | ∇ r←niladic_result 34 | :Access public 35 | (r←⎕NS'').result←'Result from niladic_result' 36 | ∇ 37 | 38 | ∇ monadic rarg 39 | :Access public 40 | ∇ 41 | 42 | ∇ r←monadic_result rarg 43 | :Access public 44 | (r←⎕NS'').(result rarg)←'Result from monadic_result'rarg 45 | ∇ 46 | 47 | ∇ larg dyadic rarg 48 | :Access public 49 | ∇ 50 | 51 | ∇ r←larg dyadic_result rarg 52 | :Access public 53 | (r←⎕NS'').(result larg rarg)←'Result from dyadic_result'larg rarg 54 | ∇ 55 | 56 | :EndClass 57 | -------------------------------------------------------------------------------- /Tests/test_PostProcess.dyalog: -------------------------------------------------------------------------------- 1 | r←test_PostProcess dummy;resp;ns;j 2 | r←'' 3 | ns←⎕NS'' 4 | j←ns.##.Jarvis.New 8080 5 | j.CodeLocation←ns 6 | j.PostProcessFn←'PostJSON' 7 | j.Paradigm←'JSON' 8 | ns.(Endpoint←{⎕NS''}) 9 | ns.(PostJSON←{⍵.Response.Payload.Message←'hello stranger'}) 10 | ns.(Get←{'hello'}) 11 | ns.(PostREST←{⍵.Response.Payload,←' stranger'}) 12 | :If 0≠⊃resp←j.Start 13 | →end⊣r←'test_PostProcess failed to start Jarvis for JSON test: ',⍕resp 14 | :EndIf 15 | resp←HttpCommand.GetJSON'post' 'localhost:8080/Endpoint' 16 | →stop↓0∊⍴r←{⍵.IsOK:'' ⋄ 'test_PostProcess (JSON) failed: ',⍕⍵}resp 17 | →stop↓0∊⍴r←{0::'test_PostProcess (JSON) response not correct' ⋄ _←÷⍵.Message≡'hello stranger'}resp.Data 18 | j.Stop 19 | j.Paradigm←'REST' 20 | j.PostProcessFn←'PostREST' 21 | j.DefaultContentType←'text/plain' 22 | :If 0≠⊃resp←j.Start 23 | →end⊣r←'test_PostProcess failed to start Jarvis for REST test: ',⍕resp 24 | :EndIf 25 | resp←HttpCommand.Get'localhost:8080' 26 | →stop↓0∊⍴r←{⍵.IsOK:'' ⋄ 'test_PostProcess (JSON) failed: ',⍕⍵}resp 27 | →stop↓0∊⍴r←{0::'test_PostProcess (REST) response not correct' ⋄ ''⊣÷⍵≡'hello stranger'}resp.Data 28 | stop:j.Stop 29 | end:⎕EX'ns' 30 | -------------------------------------------------------------------------------- /docs/settings-overview.md: -------------------------------------------------------------------------------- 1 | The settings documentation is broken up into groups of related settings as follows. 2 | 3 | | Group | Description | 4 | |--|--| 5 | | [Operational](./settings-operational.md) | Settings related to the operation of the `Jarvis` server. Examples include [`CodeLocation`](./settings-operational.md#codelocation), [`JarvisConfig`](./settings-operational.md#jarvisconfig) and [`Paradigm`](./settings-operational.md#paradigm). | 6 | | [Conga](./settings-conga.md) | Settings specific to Conga, Dyalog's TCP/IP framework. | 7 | | [JSON Mode](./settings-json.md) | Settings specific to running a **Jarvis** service in JSON mode. | 8 | | [REST Mode](./settings-rest.md) | Settings specific to running a **Jarvis** service in REST mode. | 9 | | [Session](./settings-session.md) | Settings related to using sessions with `Jarvis` to maintain server-side state. | 10 | | [User Hooks](./settings-hooks.md) | Settings that allow you to specify "hook" functions to perform tasks like application initialization, session initialization, and authentication. | 11 | | [Container](./settings-container.md) | Settings related to running a **Jarvis** service in a containerized environment like Docker. | 12 | | [CORS](./settings-cors.md) | Settings related to Cross Origin Resource Sharing which can enable calls to your **Jarvis** service to be made from webpages in other domains. | 13 | | [Shared](./settings-shared.md) | Settings that are shared by all instances of **Jarvis**.| -------------------------------------------------------------------------------- /Tests/mixed/loans.dyalog: -------------------------------------------------------------------------------- 1 | :Namespace loans 2 | 3 | ∇ r←payment npr 4 | ⍝ npr - [1] principal, [2] rate %, [3] term in years 5 | r←{0::'Error' ⋄ p r n←⍵÷1 1200(÷12) ⋄ 0.01×⌈100×p×r÷1-(1+r)*-n}npr 6 | ∇ 7 | 8 | ∇ r←afford_ns ns 9 | ⍝ returns array of what you can afford based on rates using a namespace 10 | ⍝ ns.rates - vector of rates (%) 11 | ⍝ ns.terms - vector of terms in years 12 | ⍝ ns.payments - desired payment 13 | r←{0::'Error' ⋄ r n m←⍵÷1200(÷12)1 ⋄ 0.01×⌈100×m∘.÷r(÷⍤¯1)1-(1+r)∘.*-n}ns.(rates terms payments) 14 | ∇ 15 | 16 | ∇ r←afford(rates terms payments) 17 | ⍝ returns array of what you can afford based on rates 18 | ⍝ rates - vector of rates (%) 19 | ⍝ terms - vector of terms in years 20 | ⍝ payments - desired payment 21 | r←{0::'Error' ⋄ r n m←⍵÷1200(÷12)1 ⋄ 0.01×⌈100×m∘.÷r(÷⍤¯1)1-(1+r)∘.*-n}rates terms payments 22 | ∇ 23 | 24 | ⍝ the functions below exist solely as test cases for different function syntaxes to be called by _Run 25 | 26 | ∇ niladic 27 | ⎕←(⊃⎕XSI),' called' 28 | ∇ 29 | 30 | ∇ r←niladic_result 31 | ⎕←(⊃⎕XSI),' called' 32 | r←'niladic_result result' 33 | ∇ 34 | 35 | ∇ monadic rarg 36 | ⎕←(⊃⎕XSI),' called' 37 | ∇ 38 | 39 | ∇ r←monadic_result rarg 40 | ⎕←(⊃⎕XSI),' called' 41 | r←'monadic_result result' 42 | ∇ 43 | 44 | ∇ larg dyadic rarg 45 | ⎕←(⊃⎕XSI),' called' 46 | ∇ 47 | 48 | ∇ r←larg dyadic_result rarg 49 | ⎕←(⊃⎕XSI),' called' 50 | r←'dyadic_result result' 51 | ∇ 52 | 53 | :EndNamespace 54 | -------------------------------------------------------------------------------- /Source/AutoStart.dyalog: -------------------------------------------------------------------------------- 1 | {ref}←AutoStart;empty;validParams;mask;values;params;param;value;rc;msg;getEnv;NoSession;ts;t;commits;n;debug;tonum;NoExit 2 | ⍝ Jarvis automatic startup 3 | ⍝ General logic: 4 | ⍝ Command line parameters take priority over configuration file which takes priority over default 5 | 6 | empty←0∊⍴ 7 | tonum←{0∊⍴⍵:⍵ ⋄ ∧/⊃t←⎕VFI ⍵:⊃(⎕IO+1)⊃t ⋄ ⍵} 8 | getEnv←{tonum 2 ⎕NQ'.' 'GetEnvironment'⍵} 9 | 10 | ⍝↓↓↓ JarvisConfig MUST be first in validParams 11 | validParams←∪(⊂'JarvisConfig'),((⎕NEW #.Jarvis).Config)[;1] 12 | mask←~empty¨values←getEnv¨validParams 13 | params←mask⌿validParams,⍪values 14 | NoSession←~empty getEnv'NoSession' 15 | ref←'No server running' 16 | NoExit←NoSession∨'R'=3⊃#.⎕WG'APLVersion' ⍝ no session or runtime → don't exit 17 | 18 | :If ~empty params 19 | ref←⎕NEW #.Jarvis 20 | :For (param value) :In ↓params ⍝ need to load one at a time because params can override what's in the configuration file 21 | param(ref{⍺⍺⍎⍺,'←⍵'})value 22 | :If 'JarvisConfig'≡param 23 | :If 0≠⊃(rc msg)←ref.LoadConfiguration value 24 | →∆END⊣⎕←ref←'Error loading configuration file "',value,'": ',msg 25 | :EndIf 26 | :EndIf 27 | :EndFor 28 | 29 | :If 0≠⊃(rc msg)←ref.Start 30 | →∆END⊣⎕←ref←∊⍕'Unable to start server - ',msg 31 | :EndIf 32 | 33 | :If NoExit 34 | :Trap 0 35 | :While ref.Running 36 | {}⎕DL 10 37 | :EndWhile 38 | :EndTrap 39 | :EndIf 40 | :EndIf 41 | ∆END: 42 | :If NoExit 43 | ⎕OFF 44 | :EndIf 45 | -------------------------------------------------------------------------------- /docs/settings-container.md: -------------------------------------------------------------------------------- 1 | Most Jarvis configuration options should be set using a Jarvis configuration file. A few environment variables are particularly useful in the context of running Jarvis in a container. 2 | 3 | 4 | ### `DYALOG_JARVIS_PORT` 5 | |--|--| 6 | |Description|If set, `DYALOG_JARVIS_PORT` is the port that the **Jarvis** container will listen on. It overrides both the `Jarvis` default and any port specified in the `Jarvis` configuration.| 7 | |Default|`''`| 8 | |Examples|`DYALOG_JARVIS_PORT=8888`| 9 | |Notes|`DYALOG_JARVIS_PORT` allows you to specify different port numbers for each instance of the **Jarvis** container.| 10 | 11 | ### `DYALOG_JARVIS_CODELOCATION` 12 | |--|--| 13 | |Description|If set, `DYALOG_JARVIS_CODELOCATION` is the path to a folder containing your `Jarvis` endpoint code.| 14 | |Default|`''`| 15 | |Examples|`DYALOG_JARVIS_CODELOCATION=/myJarvisApp` | 16 | 17 | ### `DYALOG_JARVIS_THREAD` 18 | |--|--| 19 | |Description|`DYALOG_JARVIS_THREAD` controls which thread `Jarvis` will run on. Valid values are: | 20 | |Default|`''`| 21 | |Examples|`DYALOG_JARVIS_THREAD=1`| 22 | |Notes|If you need to debug your **Jarvis** service in a container, you can configure Dyalog to use [RIDE](https://dyalog.github.io/ride/) and set `DYALOG_JARVIS_THREAD` to any of `debug`, `''` or `'auto'` to remotely access your **Jarvis** service.| -------------------------------------------------------------------------------- /Tests/mixed/loansclass.dyalog: -------------------------------------------------------------------------------- 1 | :Class loansclass 2 | 3 | :field public rates←5 6 4 | :field public terms←10 15 20 30 5 | :field public principals←100000 150000 200000 6 | 7 | ∇ make 8 | :Access public 9 | :Implements constructor 10 | ∇ 11 | 12 | ∇ make1 ns;name 13 | :Access public 14 | :Implements constructor 15 | :For name :In ns.⎕NL ¯2 16 | :Select name 17 | :Case 'rates' 18 | rates←ns.rates 19 | :Case 'terms' 20 | terms←ns.rates 21 | :Case 'principals' 22 | principals←ns.rates 23 | :EndSelect 24 | :EndFor 25 | ∇ 26 | 27 | ∇ r←payments 28 | :Access public 29 | ⍝ return array of payments for principals ∘. rates ∘. terms 30 | r←{0::'Error' ⋄ p r n←⍵÷1 1200(÷12) ⋄ 0.01×⌈100×p∘.×r(÷⍤¯1)1-(1+r)∘.*-n}principals rates terms 31 | ∇ 32 | 33 | ⍝ the methods below exist to be able to test the ability to execute methods of any syntax using _Run 34 | 35 | ∇ niladic 36 | :Access public 37 | ⎕←(⊃⎕XSI),' called' 38 | ∇ 39 | 40 | ∇ r←niladic_result 41 | :Access public 42 | ⎕←(⊃⎕XSI),' called' 43 | r←'niladic_result result' 44 | ∇ 45 | 46 | ∇ monadic rarg 47 | :Access public 48 | ⎕←(⊃⎕XSI),' called' 49 | ∇ 50 | 51 | ∇ r←monadic_result rarg 52 | :Access public 53 | ⎕←(⊃⎕XSI),' called' 54 | r←'monadic_result result' 55 | ∇ 56 | 57 | ∇ larg dyadic rarg 58 | :Access public 59 | ⎕←(⊃⎕XSI),' called' 60 | ∇ 61 | 62 | ∇ r←larg dyadic_result rarg 63 | :Access public 64 | ⎕←(⊃⎕XSI),' called' 65 | r←'dyadic_result result' 66 | ∇ 67 | 68 | :EndClass 69 | -------------------------------------------------------------------------------- /docs/settings-rest.md: -------------------------------------------------------------------------------- 1 | These settings apply when using **Jarvis**'s REST paradigm. 2 | 3 | ### `ParsePayload` 4 | |--|--| 5 | |Description|`ParsePayload` controls whether `Jarvis` automatically convert JSON and XML request payload to an APL format using either `⎕JSON` or `⎕XML` as appropriate.Valid settings are:| 6 | |Default|`1` - parse JSON and XML payloads| 7 | |Examples|`j.ParsePayload←0 ⍝ do not parse JSON and XML payloads`| 8 | |Notes|The format for parsed JSON payloads is controlled by [`JSONInputFormat`](./settings-json.md#jsoninputformat).| 9 | 10 | ### `RESTMethods` 11 | |--|--| 12 | |Description|`RESTMethods` specifies which HTTP methods will be supported by your REST web service. It is a comma-delimited character vector of HTTP method names and optionally, the name of the APL function that will service that HTTP method. Each comma-delimited segment consists of a case-insensitive HTTP method name (`'get' 'GET' 'gEt'` will all match GET). The method name can be optionally followed by a `'/'` and the function name which implements the handler for that HTTP method. If no function name is supplied, the function name will be the case-sensitive HTTP method. | 13 | |Default|`'Get,Post,Put,Delete,Patch,Options'`| 14 | |Examples|`j.RESTMethods←'Get,post/handlePOST'`
In this example our service will accept HTTP GET and POST requests.| 15 | |Notes|**Jarvis** does not place a restriction on the HTTP method names, meaning that you could potentially invent your own "HTTP" methods.
`j.RESTMethods←'Get,Bloofo' ⍝ allow GET and BLOOFO`.| -------------------------------------------------------------------------------- /Samples/REST/GetCustomers.aplf: -------------------------------------------------------------------------------- 1 | r←req GetCustomers parts;cind;oind;dind;pind;ExitIf 2 | ⍝ /customers[/custid[/orders[/orderid[/items[/itemid]]]]] 3 | ExitIf←→⍴∘0 4 | r←'' 5 | :Select ≢parts 6 | 7 | :Case 0 ⍝ GET customers - retrieve list of customers 8 | ExitIf req.Fail 404×1=≢Database.Customers 9 | r←TableToNS Database.Customers 10 | r.customerUri←req.MakeURI¨1↓Database.Customers[;1] 11 | 12 | :Case 1 ⍝ GET customers/custid - retrieve information for a customer 13 | 14 | ExitIf req.Fail 404×0=cind←Database.Customers[;1]lookup parts[1] 15 | r←⊃TableToNS Database.Customers[1,cind;] 16 | r.ordersUri←req.MakeURI'Orders' 17 | 18 | :Case 2 ⍝ GET customers/custid/orders 19 | 20 | ExitIf req.Fail 404×0∊oind←Database.Orders[;2]lookup parts[1] 21 | r←TableToNS Database.Orders[1,oind;] 22 | r.orderUri←req.MakeURI¨Database.Orders[oind;1] 23 | 24 | :Case 3 ⍝ GET customers/custid/orders/orderid 25 | 26 | ExitIf req.Fail 404×0∊oind←Database.Orders[;1 2]lookup parts[3 1] 27 | r←⊃TableToNS Database.Orders[1,oind;] 28 | r.itemsUri←req.MakeURI'Items' 29 | 30 | :Case 4 ⍝ GET customers/custid/orders/orderid/items 31 | 32 | ExitIf req.Fail 404×0∊Database.Orders[;1 2]lookup parts[3 1] 33 | ExitIf req.Fail 404×0∊dind←Database.Details[;1]lookup parts[3] 34 | r←TableToNS Database.Details[1,dind;] 35 | r.itemUri←req.MakeURI¨Database.Details[dind;2] 36 | 37 | :Case 5 ⍝ GET customers/custid/orders/orderid/items/itemid 38 | 39 | ExitIf req.Fail 404×0∊Database.Orders[;1 2]lookup parts[3 1] 40 | ExitIf req.Fail 404×0∊Database.Details[;1 2]lookup parts[3 5] 41 | ExitIf req.Fail 404×0∊pind←Database.Products[;1]lookup parts[5] 42 | r←TableToNS Database.Products[1,pind;] 43 | 44 | :Else 45 | req.Fail 404 46 | :EndSelect 47 | -------------------------------------------------------------------------------- /Docker/entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## This file starts the Jarvis container 4 | echo " _______ __ _ ____ _____ " 5 | echo "| __ \ \ / //\ | | / __ \ / ____|" 6 | echo "|_| | \ \_/ // \ | | | | | | | " 7 | echo " | |\ // /\ \ | | | | | | | _ " 8 | echo " ____| | | |/ / \ \| |___| |__| | |__| |" 9 | echo "|_____/ |_/_/ \_\______\____/ \_____|" 10 | echo "" 11 | echo "https://www.dyalog.com" 12 | 13 | echo "" 14 | 15 | export JARVIS=/opt/mdyalog/Jarvis 16 | ## Set default threading to "1", which means 17 | ## run Jarvis on Thread 1 and dequeue it 18 | export DYALOG_JARVIS_THREAD=${DYALOG_JARVIS_THREAD-1} 19 | export DYALOG_JARVIS_PORT=${DYALOG_JARVIS_PORT-8080} 20 | export MAXWS=${MAXWS-256M} 21 | export DYALOG=/opt/mdyalog/${VERSION}/64/unicode/ 22 | export WSPATH=${DYALOG}/ws 23 | export TERM=dumb 24 | export APL_TEXTINAPLCORE=${APL_TEXTINAPLCORE-1} 25 | export TRACE_ON_ERROR=${TRACE_ON_ERROR-0} 26 | export LOAD=$JARVIS/Source 27 | 28 | export SESSION_FILE="${SESSION_FILE-$DYALOG/default.dse}" 29 | 30 | ## if either CodeLocation or JarvisConfig are set, use them 31 | if [ -z "${CodeLocation}" ] && [ -z "${JarvisConfig}" ]; then 32 | if [ $(ls /app | grep "jarvis.json" 2>/dev/null | wc -l) -eq 1 ]; then 33 | echo "Application config found in /app/jarvis.json" 34 | export JarvisConfig=/app/$(ls /app | grep "jarvis.json") 35 | elif [ $(ls /app 2>/dev/null | wc -l) -gt 0 ]; then 36 | echo "Application code found in /app." 37 | CODEL=/app 38 | else 39 | echo "No application found in /app. Running with sample app" 40 | CODEL=$JARVIS/Samples/JSON 41 | fi 42 | export CodeLocation=${CodeLocation-$CODEL} 43 | fi 44 | 45 | cd /app 46 | 47 | if [ -n "$RIDE_INIT" ]; then 48 | $DYALOG/dyalog +s -q 49 | else 50 | $DYALOG/dyalog -s 51 | fi 52 | -------------------------------------------------------------------------------- /docs/css/main.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: APL; 3 | src: local("APL385 Unicode"), url("../assets/apl385.ttf"); 4 | } 5 | .language-APL { 6 | font-family: APL!important; 7 | line-height: 1.2em!important; 8 | } 9 | code { 10 | font-family: APL; 11 | font-size: 1em!important; 12 | padding-left: 0em!important; 13 | padding-right: 0em!important; 14 | white-space: pre; 15 | overflow-x: scroll; 16 | } 17 | .md-logo > img{ 18 | width: 7rem!important; 19 | height: 1.2rem!important; 20 | } 21 | @media screen and (max-width: 76.1875em) { 22 | .md-logo > img{ 23 | width: 10rem!important; 24 | height: 1.8rem!important; 25 | } 26 | } 27 | td:first-child { 28 | white-space: nowrap; 29 | } 30 | table.scrollable { 31 | display:block; 32 | overflow-x: auto; 33 | white-space: nowrap; 34 | } 35 | 36 | /* Custom colors */ 37 | :root { 38 | --md-primary-fg-color: #ED7F00; 39 | --md-primary-fg-color--dark: #563336; 40 | --md-default-bg-color: #F8F8F8; 41 | } 42 | /* Code copy only input (see CONTRIBUTING.md) */ 43 | pre + pre > button, pre + hr { 44 | display: none!important; 45 | } 46 | pre > code { 47 | padding-bottom: 1em!important; 48 | } 49 | hr + pre > button { 50 | top: -0.2em!important; 51 | } 52 | pre + pre , hr + pre { 53 | margin-top: -1.8em!important; 54 | } 55 | pre + pre > code, hr + pre > code { 56 | padding-top: 0.2em!important; 57 | } 58 | 59 | /* make admonition styling a la Dyalog */ 60 | .md-typeset .admonition, 61 | .md-typeset details { 62 | border-color: #ED7F00 !important; 63 | box-shadow: #ED7F00 !important; 64 | } 65 | 66 | .md-typeset :is(.admonition-title, summary){ 67 | background-color: #F3AD5b !important; 68 | } 69 | 70 | .md-typeset .admonition-title:before, 71 | .md-typeset summary:before{ 72 | background-color: #F8F8F8 !important; 73 | } 74 | 75 | .md-typeset pre > code { 76 | overflow: scroll !important; 77 | scrollbar-color: black; 78 | scrollbar-width: thin; 79 | } 80 | 81 | .md-copyright { 82 | width: 100%; 83 | } 84 | 85 | .md-typeset dd { 86 | margin-top: 0em; 87 | } 88 | 89 | .left { 90 | float: left; 91 | } 92 | 93 | .right { 94 | float: right; 95 | } -------------------------------------------------------------------------------- /Tests/ContentTypes/test_contentTypes.aplf: -------------------------------------------------------------------------------- 1 | r←{req}test_contentTypes payload;here;resp;tests;rc;contentType;data;j;h;jarvis;ok 2 | ⍝ As endpoints can return other than JSON payloads, make sure we catch data that Conga cannot transmit 3 | :If 0∊⍴payload 4 | :If 0=⎕NC'HttpCommand' ⋄ ⎕SE.UCMD'Load HttpCommand' ⋄ :EndIf 5 | :If 0=⎕NC'Jarvis' 6 | :If 0∊⍴here←1↓⊃'§'(=⊂⊢)⊃⊢/⎕NR⊃⎕SI ⍝ SALTed? 7 | :AndIf 0∊⍴here←4⊃5179⌶⊃⎕SI 8 | here←'/git/Jarvis/tests/ContentTypes/' 9 | :EndIf 10 | here←⊃1 ⎕NPARTS here 11 | :If ~⎕NEXISTS jarvis←here,'../../Source/Jarvis.dyalog' 12 | ('"',jarvis,'" not found')⎕SIGNAL 22 13 | :Else 14 | ⎕SE.Link.Import # jarvis 15 | :EndIf 16 | :EndIf 17 | j←Jarvis.New'' 18 | j.CodeLocation←⊃⎕RSI 19 | j.Start 20 | h←HttpCommand.New'post'('http://localhost:',(⍕j.Port),'/test_contentTypes') 21 | r←'' 22 | ⍝ test cases [;1] content-type, [;2] content, [;3] expected response 23 | tests←0 3⍴'' ''(0 200) 24 | tests⍪←'text/html;charset=utf-8' '

Hello ⍵○⍴⌊⍋

'(0 200) 25 | tests⍪←'application/json'(name:'foo' ⋄ value:42)(0 200) 26 | tests⍪←'application/octet-stream'(○1)(0 500) 27 | tests⍪←'application/octet-stream'(83 ⎕DR○1)(0 200) 28 | 29 | ⍝ now test various content types 30 | :For (contentType data rc) :In ↓tests 31 | h.Params←⎕NS'' 32 | h.Params.(contentType data)←contentType data 33 | resp←h.Run 34 | :If resp.(rc HttpStatus)≢rc 35 | r,←⊂h.Params.contentType,' failed: ',⍕resp 36 | :EndIf 37 | :If resp.IsOK 38 | :Select contentType 39 | :Case 'application/octet-stream' 40 | ok←data≡83 ⎕DR resp.Data 41 | :Case 'application/json' 42 | ok←(⎕JSON data)≡resp.Data 43 | :Else 44 | ok←data≡resp.Data 45 | :EndSelect 46 | :If ~ok ⋄ r,←⊂'content-type: ',contentType,' data: ',(⍕data),' did not match response data' ⋄ :EndIf 47 | :EndIf 48 | :EndFor 49 | 50 | j.Stop 51 | ⎕EX'j' 52 | :Else ⍝ we have a payload 53 | req.SetContentType payload.contentType 54 | r←payload.data 55 | :EndIf 56 | -------------------------------------------------------------------------------- /docs/using-request.md: -------------------------------------------------------------------------------- 1 | A [`Request` object](./request.md) is created for every HTTP request that `Jarvis` receives. It contains information about the request - HTTP headers, HTTP cookies, the client's IP address, certificate information (if you're using HTTPS), etc. It also contains the [`Response` namespace](./request.md#response-namespace) which will have the information to format `Jarvis`' response. 2 | 3 | `Request` is also passed as an argument to several of the ["hook" functions](./settings-hooks.md). 4 | 5 | ### Simple Authentication Example 6 | 7 | If your `Jarvis` service used [HTTP Basic](./security.md#httpbasicauthentication), `Jarvis` will populate the [`Userid`](./request.md#userid) and [`Password`](./request.md#password) fields with the credentials supplied in the request. In this example we'll use a somewhat nonsensical validation of checking if the `Password` is the reverse of the `Userid` 8 | ``` 9 | ∇ rc←Authenticate req 10 | [1] ⍝ Perform simple silly HTTP Basic authentication example 11 | [2] ⍝ check that: 12 | [3] ⍝ there is a UserID 13 | [4] ⍝ the Password is the reverse of UserID 14 | [5] ⍝ req - the request object 15 | [6] ⍝ rc - 0 if authentication passes, 1 otherwise 16 | [7] →0⍴⍨rc←0∊⍴req.UserID ⍝ fail if UserID is empty 17 | [8] rc←req.UserID≢⌽req.Password ⍝ fail if UserID is not the reverse of Password 18 | ∇ 19 | ``` 20 | or more succinctly `Authenticate←{0∊⍴⍵.UserID:1 ⋄ ⍵.UserID≢⌽⍵.Password}` 21 | 22 | ### Manipulating the Request's Response 23 | 24 | `Jarvis` will assume that all responses are of the content-type specified by [`DefaultContentType`](./settings-operational.md#defaultcontenttype) which has a default setting of `'application/json; charset=utf-8'`. You can specify a different `DefaultContentType` if most or all of your endpoints return response payloads other than JSON. You can also set the content-type in your endpoint code by using the request's [`SetContentType`](./request.md#setcontenttype) method. For example: 25 | 26 | ``` 27 | ∇ r←req ReturnHTML string 28 | [1] ⍝ Simple example of manipulating the payload 29 | [2] req.SetContentType'text/html; charset=utf-8' 30 | [3] r←'

',string,'

' 31 | ∇ 32 | ``` 33 | If you are sending files in the response payload, you can spe 34 | 35 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | def jarvis 2 | def jarvis_d20 3 | def BRANCH = env.BRANCH_NAME.toLowerCase() 4 | 5 | node ('Docker') { 6 | stage ('Checkout') { 7 | checkout scm 8 | } 9 | withDockerRegistry(credentialsId: '0435817a-5f0f-47e1-9dcc-800d85e5c335') { 10 | stage ('Build Jarvis Containers (Dyalog v19.0)') { 11 | if (BRANCH == 'master') { 12 | jarvis=docker.build('dyalog/jarvis:dyalog-v19.0', '--no-cache .') // :latest 13 | jarvis_d20=docker.build('dyalog/jarvis:dyalog-v20.0', '-f Dockerfile.20.0 --no-cache .') 14 | } else { 15 | jarvis=docker.build("dyalog/jarvis:${BRANCH}-v19.0", '--no-cache .') 16 | jarvis_d20=docker.build("dyalog/jarvis:${BRANCH}-v20.0", '-f Dockerfile.20.0 --no-cache .') 17 | } 18 | } 19 | stage ('Publish Jarvis Containers (Dyalog v19.0)') { 20 | if ((BRANCH ==~ /^v\d.*/) || (BRANCH == 'master')) { 21 | jarvis.push() 22 | } else { 23 | echo 'Not publishing containers for this checkout.' 24 | return 25 | } 26 | } 27 | stage ('Build and publish Jarvis Containers (Dyalog v20.0)') 28 | if ((BRANCH ==~ /^v\d.*/) || (BRANCH == 'master')) { 29 | // Make sure we have multiarch builders available 30 | sh ''' 31 | if ! docker buildx ls | grep multi-arch-builder ; then 32 | docker run --rm --privileged docker/binfmt:66f9012c56a8316f9244ffd7622d7c21c1f6f28d 33 | docker buildx create --use --name multi-arch-builder 34 | fi 35 | ''' 36 | // Build and publish multiarch container 37 | sh 'docker buildx build --file ./Dockerfile.20.0 --no-cache --pull --provenance=true --sbom=true --platform linux/amd64,linux/arm64 --tag dyalog/jarvis:dyalog-v20.0 --tag dyalog/jarvis:latest --progress=plain --push .' 38 | } else { 39 | echo 'Not publishing containers for this checkout.' 40 | return 41 | } 42 | } 43 | stage ('Update DockerHub README file') { 44 | withCredentials([usernamePassword(credentialsId: '0435817a-5f0f-47e1-9dcc-800d85e5c335', passwordVariable: 'DOCKER_PASS', usernameVariable: 'DOCKER_USER')]) { 45 | sh ''' 46 | cd $WORKSPACE 47 | docker pushrm -f Docker/README.md dyalog/jarvis 48 | ''' 49 | } 50 | } 51 | stage ('Cleanup') { 52 | sh 'docker image prune -f' 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /docs/json.md: -------------------------------------------------------------------------------- 1 | **Jarvis**'s JSON paradigm was developed to make it easy to expose the functionality of your APL application as a web service. The endpoints of your service are simply APL functions that take an array as a right argument and return an error as a result. 2 | ## How **Jarvis**'s JSON mode works 3 | ### You write an APL function for each endpoint of your service 4 | Each endpoint function should at a minimum take an APL array as a right argument and return an APL array as its result. The name of the endpoint is the name of your function - so, it's best to not use characters in your endpoint function names that aren't easily supported in URLs. Your functions should reside in the namespace specified by [`CodeLocation] 5 | 6 | ### The client sends a request 7 | It doesn't matter what the client is - it could be a browser, an app on a phone, Dyalog's `HttpCommand`, curl, or any program capable of sending and receiving HTTP messages. To call one of your service's endpoints, the client's request should: 8 | 9 | * Specify the 10 | * Use the HTTP POST method in order to send the request payload. 11 | * Include a `content-type: application/json` header in the request's headers. 12 | * Format its payload as JSON. 13 | 14 | !!! Example 15 | `curl -H "content-type: application/json" -X POST -d [1,3,5] http://localhost:8080/sum` 16 | 17 | !!! tip "Advanced Usage" 18 | 19 | * The [`AllowGETs`](./settings-json.md#allowgets) setting will enable HTTP GET method to be used as well - by default `AllowGETs` is disabled. 20 | * The [`AllowFormData`](./settings-json.md#allowformdata) setting will enable `Jarvis` to receive payloads that use `content-type: multipart/form-data` - by default `AllowFormData` is disabled. 21 | 22 | ### `Jarvis` receives the request 23 | When `Jarvis` receives the request, it verifies that the request is well-formed. If there is a problem parsing the request, `Jarvis` will respond to the client with a 400-series HTTP status code and message. Assuming the request is well-formed, `Jarvis` will convert the request's JSON payload to an APL array using `⎕JSON`. 24 | 25 | ### `Jarvis` calls your endpoint function 26 | `Jarvis` passes the APL array as the right argument to your endpoint function. If your function is dyadic or ambivalent, `Jarvis` will pass the [`HttpRequest`](./request.md) object as the left argument. Your function should return an APL array result. 27 | 28 | !!! tip "Advanced Usage" 29 | **Jarvis** has a few specific places where you can "inject" your own APL code to perform actions like additional request validation, authentication, and so on. Two such places are available after `Jarvis` receives the request, but before calling your endpoint function. These are: 30 | 31 | * [`ValidateRequestFn`](./settings-hooks.md#validaterequestfn) specifies the name of a function to call for every request that `Jarvis` receives. 32 | * [`AuthenticateFn`](./settings-hooks.md#authenticatefn) specifies the name of a function to call to perform authentication. 33 | 34 | ### `Jarvis` sends the response to the client 35 | `Jarvis` will convert the APL array result into JSON format using `⎕JSON⍠'HighRank' 'Split'` and send the JSON back to the client as the payload of the response. -------------------------------------------------------------------------------- /docs/settings-session.md: -------------------------------------------------------------------------------- 1 | A stateless web service means that each request from a client to the server is treated as an independent transaction that is unrelated to any previous request. In other words, the server does not store any information about the state of the client between requests. There are many good reasons for implementing a stateless web service including improved scalability, reliability, and independence. However, in some cases it may make sense to maintain some state on the server. **Jarvis**'s sessions are intended to allow you to maintain state in the server. 2 | 3 | See [Using Sessions](./sessions.md) for more information. 4 | 5 | ### `SessionTimeout` 6 | |--|--| 7 | |Description| `SessionTimeout` controls whether `Jarvis` will use sessions. It also specifies how long before a session will time out and be removed. Valid settings are:| 8 | |Default|`0` - do not use sessions| 9 | |Examples|`j.SessionTimeout←10 ⍝ timeout after 10 minutes of inactivity`| 10 | 11 | ### `SessionIdHeader` 12 | |--|--| 13 | |Description| `SessionIdHeader` is the name of the HTTP header or HTTP cookie that will contain the session identifier. Every sessioned request must include this session ID in order to access the session state information on the server.| 14 | |Default|`'Jarvis-SessionID'`| 15 | |Examples|`j.SessionIdHeader←'gandalf'`| 16 | 17 | ### `SessionUseCookie` 18 | |--|--| 19 | |Description| `SessionUseCookie` controls whether the session id is sent using an HTTP header or an HTTP cookie. In either case, the header or the cookie name will be specified by `SessionIdHeader`. Valid settings are: