├── .gitignore ├── CI └── CI.md ├── Demos ├── Client.demo └── Server.demo ├── Distribution ├── Jarvis.dws └── JarvisService.dws ├── Docker ├── README.md └── entrypoint ├── Dockerfile ├── Dockerfile.20.0 ├── Jarvis.demo ├── Jarvis.dyalogbuild ├── JarvisService.dyalogbuild ├── Jenkinsfile ├── LICENSE ├── README.md ├── Samples ├── JSON │ ├── GetSign.dyalog │ ├── GetSignObject.dyalog │ ├── GetSignWithRequest.dyalog │ └── Utils │ │ └── Reverse.dyalog ├── JSON_Namespace │ └── test.dyalog └── REST │ ├── Authenticate.aplf │ ├── Database.dcf │ ├── Delete.aplf │ ├── Get.aplf │ ├── GetCustomers.aplf │ ├── GetOrders.aplf │ ├── GetProducts.aplf │ ├── InitSession.aplf │ ├── Initialize.aplf │ ├── Post.aplf │ ├── Put.aplf │ ├── TableToNS.aplf │ ├── Test.aplf │ ├── config.json │ └── lookup.aplf ├── Service ├── Config.dyalog ├── JarvisService.dyalog └── SysLog.dyalog ├── Source ├── AutoStart.dyalog ├── Jarvis.dyalog ├── Run.aplf └── Updates.dyalog ├── Tests ├── AllowGETs │ └── jarvisconfig.json ├── Secure │ ├── PickCert.dyalog │ └── TestSecure.dyalog ├── login │ ├── Authenticate.dyalog │ ├── jarvisconfig.json │ ├── loginneeded.dyalog │ ├── nologinneeded.dyalog │ └── payloadcreds.dyalog ├── mixed │ ├── Excluded.dyalog │ ├── demo.txt │ ├── loans.dyalog │ ├── loansclass.dyalog │ └── reverse.dyalog ├── run │ └── testClass.dyalog ├── sessions │ ├── Add.dyalog │ ├── InitializeSession.dyalog │ ├── Subtract.dyalog │ └── jarvisconfig.json ├── setup.dyalog ├── teardown.dyalog ├── test_KillOnDisconnect.dyalog ├── test_PostProcess.dyalog ├── test_sessions.dyalog ├── testconfig.json └── unit.dyalogtest ├── apl-package.json ├── docs ├── LICENSE.md ├── assets │ └── apl385.ttf ├── concepts.md ├── css │ └── main.css ├── docker.md ├── img │ ├── dyalog-white.svg │ ├── favicon-32.png │ └── sample.png ├── index.md ├── json.md ├── methods-instance.md ├── methods-shared.md ├── reference.md ├── release-notes.md ├── request.md ├── rest.md ├── security.md ├── sessions.md ├── settings-conga.md ├── settings-container.md ├── settings-cors.md ├── settings-hooks.md ├── settings-json.md ├── settings-operational.md ├── settings-overview.md ├── settings-rest.md ├── settings-session.md ├── settings-shared.md └── using.md └── mkdocs.yml /.gitignore: -------------------------------------------------------------------------------- 1 | site/ 2 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /Demos/Client.demo: -------------------------------------------------------------------------------- 1 | ⍝ Calling Jarvis 2 | )clear 3 | 4 | ]load HttpCommand 5 | cmd←⎕NEW HttpCommand 6 | cmd.(Command URL)←'POST' 'localhost:8080/GetSign' 7 | cmd.Headers⍪←'content-type' 'application/json' 8 | cmd.Params←⎕JSON 10 31 ⍝ '[10,31]' 9 | q←cmd.Run 10 | q.(rc Data) 11 | 12 | cmd.Params←'["October",31]' 13 | q←cmd.Run 14 | q.(rc HttpStatus HttpMessage) 15 | q.Data 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Distribution/Jarvis.dws: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/Jarvis/ca20c6d1d1b1404c1b65ba7fd8428723f23f19c5/Distribution/Jarvis.dws -------------------------------------------------------------------------------- /Distribution/JarvisService.dws: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/Jarvis/ca20c6d1d1b1404c1b65ba7fd8428723f23f19c5/Distribution/JarvisService.dws -------------------------------------------------------------------------------- /Docker/README.md: -------------------------------------------------------------------------------- 1 | ## Jarvis web service framework 2 | 3 | Jarvis is Dyalog's web service framework, written in Dyalog APL. For more information about Jarvis, see (the Jarvis GitHub repository)[https://github.com/Dyalog/jarvis]. The `dyalog/jarvis` container is built from the Docker subdirectory in that repository, and is designed to make it very easy to deploy Jarvis-based applications. 4 | 5 | ## Using the container 6 | 7 | If `/path/to/app` contains the application that Jarvis is to serve and `7777` is the port that you would like the service to appear on, then all you need to do to start running a containerised Jarvis server is to use docker run to start the `dyalog/jarvis` container, using the `-v` switch to mount the directory under the name `/app` and `-p` to map the port number to 8080, which is the port number that Jarvis will use inside the container: 8 | 9 | ```sh 10 | docker run -p 7777:8080 -v /path/to/app:/app dyalog/jarvis 11 | ``` 12 | ## Demo Application 13 | 14 | If you do not map a directory into the container, it will serve up the default application which can be found in the `Samples\JSON` folder in the Jarvis repository. If you direct a web browser at the exposed port, Jarvis will present a simple interactive interface. You can test that it is working by entering "GetSign" as the method to execute, and a date of birth in the form "[mm,dd]" as JSON data, and clicking "send". 15 | 16 | ## Debugging 17 | 18 | See the description of the `dyalog/dyalog` container for information on debugging and other fundamentals. The `dyalog/jarvis` container is built upon that container, adding the code for Jarvis as the main application to be run. 19 | 20 | ## Configuration 21 | 22 | Most Jarvis configuration options should be set using a Jarvis configuration file, which you can read about in the Jarvis documentation. A couple of environment variables are particularly useful in the context of running Jarvis in a container. 23 | 24 | | Variable Name | Description | 25 | | -------------------------- | ------------------------------------------------------------ | 26 | | DYALOG_JARVIS_CODELOCATION | The name of the directory (as seen from inside the container) where the application code resides (default is `/app`) | 27 | | DYALOG_JARVIS_PORT | The port number to use inside the container (default is 8080) | 28 | 29 | You can set environment variables to modify the behaviour of the container. For example, you could insert`-e DYALOG_JARVIS_CODELOCATION=/code ` into the `docker run` command (assuming that was where you had placed the code). 30 | 31 | ## Licence 32 | 33 | Dyalog is free for non-commercial use but is not free software. Please see [here](https://www.dyalog.com/prices-and-licences.htm) for our Licence Agreement and full Terms and Conditions. Note that: 34 | 35 | * Commercial re-distribution of software that includes Dyalog requires a [Run Time Licence](https://www.dyalog.com/prices-and-licences.htm#runtimelic). If you do not have a commercial licence, and you make images available for download, this constitutes acceptance of the default Run Time Licences, which allows non-commercial and limited commercial distribution. 36 | * If you create docker images which include Dyalog APL in addition to your own work and make them available for download, you must include the LICENSE file in a prominent location and include instructions which make reference to it. 37 | 38 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/techpreview: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 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /Jarvis.dyalogbuild: -------------------------------------------------------------------------------- 1 | DyalogBuild: 0.1 2 | ID : Jarvis, Version=1.0 3 | Description: Jarvis Web Service Framework 4 | Defaults : ⎕IO←⎕ML←1 5 | TARGET : Distribution/Jarvis.dws 6 | 7 | APL : Source/*.dyalog, Target=# 8 | LIB : HttpCommand, Target=# 9 | LX : ⍎(⎕IO+0∊⍴2⎕NQ'.' 'GetEnvironment' 'AttachDebugger')⊃'⎕←''Autostart not run because AttachDebugger was set''' 'Server←AutoStart' -------------------------------------------------------------------------------- /JarvisService.dyalogbuild: -------------------------------------------------------------------------------- 1 | DyalogBuild: 0.1 2 | ID : JarvisService, Version=1.0 3 | Description: Jarvis Web Service Framework as a service 4 | Defaults : ⎕IO←⎕ML←1 5 | TARGET : Distribution/JarvisService.dws 6 | 7 | APL : Source/Jarvis.dyalog, Target=# 8 | APL : Service/SysLog.dyalog, Target=# 9 | APL : Service/JarvisService.dyalog, Target=# 10 | NS : Samples/JSON/*.dyalog, Target=#.Code 11 | APL : Service/Config.dyalog, Target=# 12 | LIB : HttpCommand, Target=# 13 | LX : JarvisService.StartService '' -------------------------------------------------------------------------------- /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', '--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 --platform linux/amd64,linux/arm64 --tag dyalog/jarvis:dyalog-v20.0 --progress=plain --push .' 38 | } else { 39 | echo 'Not publishing containers for this checkout.' 40 | return 41 | } 42 | } 43 | stage ('Cleanup') { 44 | sh 'docker image prune -f' 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jarvis 2 | APL-based web service framework supporting JSON or REST 3 | 4 | See [the wiki](https://github.com/Dyalog/Jarvis/wiki) for documentation, or [Samples](https://github.com/Dyalog/Jarvis/tree/master/Samples) for examples with usage under [Demos](https://github.com/Dyalog/Jarvis/tree/master/Demos) (using two interpreters) and in [Jarvis.demo](https://github.com/Dyalog/Jarvis/blob/master/Jarvis.demo) (using a single interpreter). 5 | 6 | Jarvis is also available as a public Docker container on [DockerHub](https://hub.docker.com/r/dyalog/jarvis). 7 | -------------------------------------------------------------------------------- /Samples/JSON/GetSign.dyalog: -------------------------------------------------------------------------------- 1 |  sign←GetSign date;dates;signs 2 | ⍝ Compute sign of the Zodiac from a 2-element integer vector containing [Month,Day] 3 | signs←13⍴'Capricorn' 'Aquarius' 'Pisces' 'Aries' 'Taurus' 'Gemini' 'Cancer' 'Leo' 'Virgo' 'Libra' 'Scorpio' 'Sagittarius' 4 | dates←119 218 320 419 520 620 722 822 922 1022 1121 1221 5 | sign←signs⊃⍨1+dates⍸100⊥2↑date 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 | -------------------------------------------------------------------------------- /Samples/JSON/GetSignWithRequest.dyalog: -------------------------------------------------------------------------------- 1 |  r←req GetSignWithRequest date;dates;signs 2 | ⍝ Compute sign of the Zodiac from a 2-element integer vector containing [Month,Day] 3 | r←⎕NS'' 4 | signs←13⍴'Capricorn' 'Aquarius' 'Pisces' 'Aries' 'Taurus' 'Gemini' 'Cancer' 'Leo' 'Virgo' 'Libra' 'Scorpio' 'Sagittarius' 5 | dates←119 218 320 419 520 620 722 822 922 1022 1121 1221 6 | r.sign←signs⊃⍨1+dates⍸100⊥2↑date 7 | r.ipAddr←req.PeerAddr 8 | :If ~0∊⍴req.PeerCert 9 | r.certSubj←req.PeerCert.Formatted.Subject 10 | :EndIf 11 | -------------------------------------------------------------------------------- /Samples/JSON/Utils/Reverse.dyalog: -------------------------------------------------------------------------------- 1 |  r←Reverse data 2 | r←⌽data 3 | -------------------------------------------------------------------------------- /Samples/JSON_Namespace/test.dyalog: -------------------------------------------------------------------------------- 1 | :namespace test 2 | ∇ sign←GetSign date;dates;signs 3 | ⍝ Compute sign of the Zodiac from a 2-element integer vector containing [Month,Day] 4 | 5 | signs←13⍴'Capricorn' 'Aquarius' 'Pisces' 'Aries' 'Taurus' 'Gemini' 'Cancer' 'Leo' 'Virgo' 'Libra' 'Scorpio' 'Sagittarius' 6 | dates←119 218 320 419 520 620 722 822 922 1022 1121 1221 7 | sign←signs⊃⍨1+dates⍸100⊥2↑date 8 | ∇ 9 | :endnamespace 10 | -------------------------------------------------------------------------------- /Samples/REST/Authenticate.aplf: -------------------------------------------------------------------------------- 1 | r←Authenticate req;ind 2 | ⍝ simple authentication 3 | →r←0 4 | →0⍴⍨r←(≢Database.Users)0∊⍴msg 62 | level log.Write msg ⍝ Add severity ?? 63 | :EndIf 64 | r←'(',('EWI'[1+3|¯1+(1 2 3 'EWIewi')⍳⊃level]),') ',msg 65 | ∇ 66 | 67 | 68 | ∇ HashDefine 69 | :Access public shared 70 | ⍝ Service states are as follows: 71 | SERVICE_STOPPED←1 72 | SERVICE_START_PENDING←2 73 | SERVICE_STOP_PENDING←3 74 | SERVICE_RUNNING←4 75 | SERVICE_CONTINUE_PENDING←5 76 | SERVICE_PAUSE_PENDING←6 77 | SERVICE_PAUSED←7 78 | 79 | ⍝ Service Control Codes (actions) are as follows: 80 | SERVICE_CONTROL_STOP←1 81 | SERVICE_CONTROL_PAUSE←2 82 | SERVICE_CONTROL_CONTINUE←3 83 | SERVICE_CONTROL_INTERROGATE←4 84 | SERVICE_CONTROL_SHUTDOWN←5 85 | SERVICE_CONTROL_PARAMCHANGE←6 86 | SERVICE_CONTROL_NETBINDADD←7 87 | SERVICE_CONTROL_NETBINDREMOVE←8 88 | SERVICE_CONTROL_NETBINDENABLE←9 89 | SERVICE_CONTROL_NETBINDDISABLE←10 90 | SERVICE_CONTROL_DEVICEEVENT←11 91 | SERVICE_CONTROL_HARDWAREPROFILECHANGE←12 92 | SERVICE_CONTROL_POWEREVENT←13 93 | SERVICE_CONTROL_SESSIONCHANGE←14 94 | SERVICE_CONTROL_PRESHUTDOWN←15 95 | 96 | ∇ 97 | 98 | 99 | ∇ r←ServiceHandler(obj event action state);sink 100 | :Access public shared 101 | 102 | ⍝ Callback to handle notifications from the SCM 103 | 104 | ⍝ Note that the interpreter has already responded 105 | ⍝ automatically to the SCM with the corresponding 106 | ⍝ "_PENDING" message prior to this callback being reached 107 | 108 | ⍝ This callback uses the SetServiceState Method to confirm 109 | ⍝ to the SCM that the requested state has been reached 110 | 111 | r←0 ⍝ so returns a 0 result (the event has been handled, 112 | ⍝ no further action required) 113 | 114 | ⍝ It stores the desired state in global ServiceState to 115 | ⍝ notify the application code which must take appropriate 116 | ⍝ action. In particular, it must respond to a "STOP or 117 | ⍝ "SHUTDOWN" by terminating the APL session 118 | :Select #.ServiceControl←action 119 | :CaseList SERVICE_CONTROL_STOP SERVICE_CONTROL_SHUTDOWN 120 | #.ServiceState←SERVICE_STOPPED 121 | state[4 5 6 7]←0 122 | ⎕←'ServiceHandler Stop :',⍕#.ServiceControl 123 | :Case SERVICE_CONTROL_PAUSE 124 | #.ServiceState←SERVICE_PAUSED 125 | ⎕←'ServiceHandler Pause :',⍕#.ServiceControl 126 | :Case SERVICE_CONTROL_CONTINUE 127 | #.ServiceState←SERVICE_RUNNING 128 | ⎕←'ServiceHandler Continue :',⍕#.ServiceControl 129 | :Case SERVICE_CONTROL_INTERROGATE 130 | ⍝ do nothing sometimes it interrogates every 10 secs 131 | :Else 132 | ⎕←'ServiceHandler:',⍕#.ServiceControl 133 | :If state[2]=SERVICE_START_PENDING 134 | #.ServiceState←SERVICE_RUNNING 135 | :EndIf 136 | :EndSelect 137 | state[2]←#.ServiceState 138 | sink←2 ⎕NQ'.' 'SetServiceState'state 139 | ∇ 140 | 141 | ∇ ServiceMain arg 142 | :Access public 143 | 'I'Log'ServiceMain Starting' 144 | 'I'Log'ServiceMain: ',,⍕Start ⍝ Start Jarvis serice 145 | :While #.ServiceState≠SERVICE_STOPPED 146 | :If ~#.ServiceControl∊0 SERVICE_CONTROL_INTERROGATE ⋄ 'I'Log'ServiceControl=',⍕#.ServiceControl ⋄ :EndIf 147 | 148 | :Select #.ServiceControl 149 | :Case SERVICE_CONTROL_STOP 150 | 'I'Log'ServiceMain: ',,⍕Stop 151 | :Case SERVICE_CONTROL_PAUSE 152 | 'I'Log'ServiceMain: ',,⍕Pause 153 | :Case SERVICE_CONTROL_CONTINUE 154 | 'I'Log'ServiceMain: ',,⍕Start 155 | :Case SERVICE_CONTROL_INTERROGATE 156 | :Case SERVICE_CONTROL_SHUTDOWN 157 | 'I'Log'ServiceMain: ',,⍕Stop 158 | :Case SERVICE_CONTROL_PARAMCHANGE 159 | :Case SERVICE_CONTROL_NETBINDADD 160 | :Case SERVICE_CONTROL_NETBINDREMOVE 161 | :Case SERVICE_CONTROL_NETBINDENABLE 162 | :Case SERVICE_CONTROL_NETBINDDISABLE 163 | :Case SERVICE_CONTROL_DEVICEEVENT 164 | :Case SERVICE_CONTROL_HARDWAREPROFILECHANGE 165 | :Case SERVICE_CONTROL_POWEREVENT 166 | :Case SERVICE_CONTROL_SESSIONCHANGE 167 | :Case SERVICE_CONTROL_PRESHUTDOWN 168 | 'I'Log'ServiceMain: ',,⍕Stop 169 | :Case 0 170 | 171 | :EndSelect 172 | 173 | :Select #.ServiceState 174 | :Case SERVICE_STOPPED 175 | :Case SERVICE_START_PENDING 176 | :Case SERVICE_STOP_PENDING 177 | :Case SERVICE_RUNNING 178 | :Case SERVICE_CONTINUE_PENDING 179 | :Case SERVICE_PAUSE_PENDING 180 | :Case SERVICE_PAUSED 181 | :EndSelect 182 | #.ServiceControl←0 ⍝ Reset (we only want to log changes) 183 | ⎕DL 10 ⍝ Just to prevent busy loop 184 | :EndWhile 185 | 186 | ⎕OFF 0 187 | 188 | ∇ 189 | 190 | ∇ r←ClassName 191 | :Access public shared 192 | r←⍕⊃⊃⎕CLASS ⎕THIS 193 | ∇ 194 | 195 | ∇ Describe;wsid;cmdlineargs;apl;service;a;s 196 | :Access public shared 197 | ⎕←'This is intended to run as a service' 198 | wsid←⎕WSID 199 | cmdlineargs←2 ⎕NQ'.' 'GetCommandLineArgs' 200 | apl←⊃cmdlineargs 201 | service←2⊃⎕NPARTS wsid 202 | :For a :In cmdlineargs 203 | :If 0<≢s←'APL_ServiceEvtInit='{⍺≡(≢⍺)↑⍵:(≢⍺)↓⍵ ⋄ ''}a 204 | #.SysLog.CreateEventSource s'Dyalog APL' 205 | ⎕←'Event src created' 206 | ⎕OFF 0 207 | :EndIf 208 | :EndFor 209 | 210 | ⎕←wsid 211 | ⎕←cmdlineargs 212 | ⎕←'To install/uninstall/initalize run as Administrator' 213 | ⎕←'CommandLines:' 214 | ⎕←apl,' ',wsid,' APL_ServiceInstall=',service,' ',2↓cmdlineargs 215 | ⎕←apl,' ',wsid,' APL_ServiceUninstall=',service,' ',2↓cmdlineargs 216 | ⎕←apl,' ',wsid,' APL_ServiceEvtInit=',service 217 | ∇ 218 | 219 | ∇ StartService MyServiceFunction 220 | ⍝ This is the ⎕lx entry point to run Jarvis as a service 221 | 222 | :Access public shared 223 | ⍝ ∘∘∘ ⍝ Remove comment to make service start wait for Ride Connection 224 | :If 'W'≠3⊃#.⎕WG'APLVersion' 225 | ⎕←'This workspace only works using Dyalog APL for Windows version 14.0 or later' 226 | :Return 227 | :EndIf 228 | :If 0∊⍴2 ⎕NQ'.' 'GetEnvironment' 'RunAsService' 229 | Describe 230 | :Return 231 | :EndIf 232 | 233 | ⍝ Define SCM constants 234 | HashDefine 235 | ⍝ Set up callback to handle SCM notifications 236 | '.'⎕WS'Event' 'ServiceNotification'(ClassName,'.ServiceHandler') 237 | ⍝ Global variable defines current state of the service 238 | #.ServiceState←SERVICE_RUNNING 239 | ⍝ Global variable defines last SCM notification to the service 240 | #.ServiceControl←0 241 | ⍝ Application code runs in a separate thread 242 | #.js←⎕NEW JarvisService 243 | #.js.ConfigFile←#.Config 244 | #.js.CodeLocation←#.Code 245 | #.js.ServiceMain&0 246 | ⎕DL 1 247 | ⍎MyServiceFunction 248 | ⎕DQ'.' 249 | ⎕OFF 250 | ∇ 251 | 252 | :EndClass 253 | -------------------------------------------------------------------------------- /Service/SysLog.dyalog: -------------------------------------------------------------------------------- 1 | :Class SysLog 2 | ⍝ Implements simple interface to write events to system log files 3 | ⍝ Windows capability is implemented 4 | ⍝ *NIX will be added in the future 5 | 6 | ⎕IO←⎕ML←1 7 | 8 | :field Source←'' ⍝ Source is the name of the application 9 | :field Log←'' ⍝ Log is the name of the log (under Windows, it defaults to "Application") 10 | :field Machine←,'.' ⍝ means this local machine, could be another machine on the network 11 | :field eventlog 12 | 13 | 14 | isWin←'W'=1↑3⊃'.'⎕WG 'APLVersion' 15 | 16 | ∇ r←isAdmin 17 | ⍝ check is user if running as an administrator 18 | :Access public shared 19 | r←{0::0 ⋄ isWin:⍎⎕NA'I shell32|IsUserAnAdmin' ⋄ 0}'' 20 | ∇ 21 | 22 | ∇ SetUsing 23 | ⎕USING←'System' 'System.Diagnostics,system.dll' 24 | ∇ 25 | 26 | ∇ Make source 27 | :Implements constructor 28 | :Access public 29 | 'This utility runs only under Windows at this time'⎕SIGNAL 11/⍨~isWin 30 | ('Event source "',source,'" does not exist')⎕SIGNAL 6/⍨~EventSourceExists source 31 | Source←source 32 | eventlog←⎕NEW EventLog 33 | eventlog.Source←⊂,source 34 | ∇ 35 | 36 | ∇ {level}Write message;type 37 | :Access public 38 | ⍝ message is message to write 39 | ⍝ level is one of: 1 'E' 'e' for error message 40 | ⍝ 2 'W' 'w' for warning message 41 | ⍝ 3 'I' 'i' for informational message (the default) 42 | level←{6::⍵ ⋄ level}3 43 | :If isWin 44 | type←(EventLogEntryType.(Error Warning Information))[1+3|¯1+(1 2 3, 'EWIewi')⍳⊃level] 45 | eventlog.WriteEntry(message type) 46 | :Else 47 | ∘∘∘ 48 | :EndIf 49 | ∇ 50 | ∇ {level}WriteLog(source message);type 51 | :Access public shared 52 | ⍝ message is message to write 53 | ⍝ level is one of: 1 'E' 'e' for error message 54 | ⍝ 2 'W' 'w' for warning message 55 | ⍝ 3 'I' 'i' for informational message (the default) 56 | level←{6::⍵ ⋄ level}3 57 | :If isWin 58 | SetUsing 59 | type←(EventLogEntryType.(Error Warning Information))[1+3|¯1+(1 2 3, 'EWIewi')⍳⊃level] 60 | EventLog.WriteEntry(source message type) 61 | :Else 62 | ∘∘∘ 63 | :EndIf 64 | ∇ 65 | 66 | 67 | ∇ r←EventSourceExists source 68 | :Access public shared 69 | :If isWin 70 | :Trap 90 71 | SetUsing 72 | r←EventLog.SourceExists⊂,source 73 | :Else 74 | r←0 75 | :EndTrap 76 | :Else 77 | ∘∘∘ 78 | :EndIf 79 | ∇ 80 | 81 | ∇ CreateEventSource args;log;source 82 | :Access public shared 83 | :If isWin 84 | 'You must be running as an administrator to use this'⎕SIGNAL 11/⍨~isAdmin 85 | :If 1<|≡args 86 | source log←args 87 | :Else 88 | source←,args ⋄ log←'' 89 | :EndIf 90 | ('Event source "',source,'" already exists')⎕SIGNAL 11/⍨EventSourceExists source 91 | EventLog.CreateEventSource(source log) 92 | :Else 93 | ∘∘∘ 94 | :EndIf 95 | ∇ 96 | 97 | ∇ DeleteLog log 98 | :Access public shared 99 | :If isWin 100 | 'You must be running as an administrator to use this'⎕SIGNAL 11/⍨~isAdmin 101 | SetUsing 102 | EventLog.Delete⊂,log 103 | :Else 104 | ∘∘∘ 105 | :EndIf 106 | ∇ 107 | 108 | ∇ DeleteEventSource source 109 | :Access public shared 110 | :If isWin 111 | 'You must be running as an administrator to use this'⎕SIGNAL 11/⍨~isAdmin 112 | ('Event source "',source,'" does not exist')⎕SIGNAL 6/⍨~EventSourceExists source 113 | EventLog.DeleteEventSource⊂source 114 | :Else 115 | ∘∘∘ 116 | :EndIf 117 | ∇ 118 | 119 | ∇ r←LogExists log 120 | :Access public shared 121 | :If isWin 122 | :Trap 0 123 | SetUsing 124 | EventLog.Exists⊂,log 125 | :Else 126 | r←0 127 | :EndTrap 128 | :Else 129 | ∘∘∘ 130 | :EndIf 131 | ∇ 132 | 133 | ∇ r←LogNameFromSourceName source 134 | :Access public shared 135 | :If isWin 136 | ('Event source "',source,'" does not exist')⎕SIGNAL 6/⍨~EventSourceExists source 137 | r←EventLog.LogNameFromSourceName(source(,'.')) 138 | :Else 139 | ∘∘∘ 140 | :EndIf 141 | ∇ 142 | :EndClass 143 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Source/Run.aplf: -------------------------------------------------------------------------------- 1 |  Run;getEnv;JARVIS 2 | ⍝ Run function suitable for use with LOAD=$JARVIS/Source 3 | 4 | getEnv←{2 ⎕NQ '.' 'GetEnvironment' ⍵} 5 | 6 | :If 0∊⍴getEnv 'AttachDebugger' 7 | Server←AutoStart 8 | :Else 9 | ⎕←'Autostart not run because AttachDebugger was set' 10 | :EndIf 11 | -------------------------------------------------------------------------------- /Source/Updates.dyalog: -------------------------------------------------------------------------------- 1 |  Updates;t;n;commits 2 | ⍝ check up to last 5 updates to repository 3 | :Trap 0 4 | t←HttpCommand.Get'http://api.github.com/repos/Dyalog/Jarvis/commits' 5 | n←5⌊≢commits←⎕JSON t.Data ⍝ last commit should be for this workspace 6 | ⎕←'The last ',(⍕n),' commits to repository http://github.com/Dyalog/DServer are:' 7 | ⎕←'Date' 'Description'⍪↑(n↑commits).commit.(author.date(↑message((~∊)⊆⊣)⎕UCS 13 10)) 8 | :Else 9 | ⎕←'!! unable to check updates - ',⍕2↑⎕DM 10 | :EndTrap 11 | -------------------------------------------------------------------------------- /Tests/AllowGETs/jarvisconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "AllowGETs" : 1, 3 | "Port" : 22224, 4 | "HTMLInterface" : 0 5 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Tests/login/Authenticate.dyalog: -------------------------------------------------------------------------------- 1 | r←Authenticate req 2 | :If ∨/'nologinneeded'⍷req.Endpoint 3 | →r←0 4 | :EndIf 5 | :If ∨/'payloadcreds'⍷req.Endpoint 6 | →0⊣r←~('uid'≡req.Payload.UserID)∧'pwd'≡req.Payload.Password 7 | :EndIf 8 | r←~('uid'≡req.UserID)∧'pwd'≡req.Password 9 | -------------------------------------------------------------------------------- /Tests/login/jarvisconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "AuthenticateFn" : "Authenticate", 3 | "Port" : 22224 4 | } -------------------------------------------------------------------------------- /Tests/login/loginneeded.dyalog: -------------------------------------------------------------------------------- 1 | r←loginneeded arg 2 | r←(⊃⎕SI)arg 3 | -------------------------------------------------------------------------------- /Tests/login/nologinneeded.dyalog: -------------------------------------------------------------------------------- 1 |  r←nologinneeded arg 2 | r←(⊃⎕SI)arg 3 | -------------------------------------------------------------------------------- /Tests/login/payloadcreds.dyalog: -------------------------------------------------------------------------------- 1 | r←payloadcreds arg 2 | r←(⊃⎕SI)arg 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Tests/mixed/demo.txt: -------------------------------------------------------------------------------- 1 | ⍝ Demo/Test script 2 | &' 3 | ]load HttpCommand 4 | ]load /git/JSONServer/Source/JSONServer 5 | )copy dfns disp 6 | 7 | 8 | &' 9 | ⍝ --- define a couple utilities --- 10 | showJSON←{0∊⍴⍵:⍵ ⋄ 1(⎕JSON⍠'Compact' 0)0⎕JSON ⍵} 11 | showReq←{req←disp 'URL' 'Params',⍪⍺.(URL Params) ⋄ resp←disp 'HTTP Status' 'Data',⍪(⍕⍵.(HttpStatus HttpMessage))(showJSON ⍵.Data) ⋄ ⍪,'Request' 'Response',⍪req resp} 12 | 13 | &' 14 | ⍝ --- Create the Server --- 15 | &' 16 | srv←⎕NEW JSONServer ⍝ create the server 17 | srv.ClassInterface←1 ⍝ turn class interface on 18 | srv.CodeLocation←'/git/JSONServer/Tests/mixed/' ⍝ where to load the code from 19 | srv.Port←8080 ⍝ port to listen on 20 | srv.ExcludeFns←'_*' '[A-Z].*' ⍝ exclude any functions beginning with _ or uppercase 21 | srv.Start ⍝ start the server 22 | 23 | &' 24 | ⍝ --- Create the client --- 25 | &' 26 | cmd←⎕NEW HttpCommand ⍝ create a client 27 | baseURL←'http://localhost:8080/' ⍝ base URL 28 | cmd.Command←'post' ⍝ all JSONServer requests are "post" 29 | 'content-type' cmd.AddHeader 'application/json' ⍝ set the content-type 30 | 31 | &' 32 | ⍝ --- Simple function call --- 33 | &' 34 | #.CodeLocation.⎕VR 'reverse' 35 | ⎕←cmd.URL←baseURL,'reverse' ⍝ function to call 36 | cmd.Params←'"Dyalog JSONServer"' ⍝ data to pass (simple string in this case) 37 | resp←cmd.Run ⍝ submit the request 38 | &' 39 | ⍝ --- HttpCommand.Run returns a namespace --- 40 | resp.⎕NL ¯2 ¯9 41 | resp.(HttpStatus HttpMessage) ⍝ check the status 42 | resp.Data ⍝ show the response's data 43 | cmd showReq resp 44 | &' 45 | ⍝ --- functions in namespaces can be referenced directly in the URL --- 46 | ⎕←cmd.URL←baseURL,'loans/payment' ⍝ loan payment calulator 47 | cmd.Params←'[100000,5.5,30]' ⍝ principal, interest rate, years 48 | cmd showReq cmd.Run 49 | &' 50 | ⍝ --- ExcludedFns is used to exclude or hide functions from JSONServer --- 51 | &' 52 | cmd.URL←baseURL,'Excluded' ⍝ should not be allowed because of ExcludeFns 53 | cmd showReq cmd.Run ⍝ submit the request 54 | srv.ExcludeFns 55 | &' 56 | srv.ExcludeFns←'_*' ⍝ remove the [A-Z].* exclusion 57 | &' 58 | cmd showReq cmd.Run 59 | &' 60 | ⍝ --- Class Interface --- 61 | ⍝ uses "built-in" functions _New _Get _Set _Run _Classes _Instances _Serialize 62 | &' 63 | cmd.URL←baseURL,'_Classes' ⍝ what classes are available? 64 | cmd.Params←'' ⍝ no arguments 65 | cmd showReq cmd.Run 66 | &' 67 | ⍝ Note: Every class interface function returns "rc" and "message" 68 | &' 69 | ⍝ --- Create an instance --- 70 | &' 71 | cmd.URL←baseURL,'_New' 72 | cmd.Params←'{"className":"loansclass"}' 73 | resp←cmd.Run ⍝ submit the request 74 | cmd showReq resp 75 | &' 76 | instance←(⎕JSON resp.Data).instanceName ⍝ grab the instance name for later use 77 | &' 78 | ⍝ --- Let's try creating an instance of a class that doesn't exist --- 79 | &' 80 | cmd.Params←'{"className":"BadClassName"}' 81 | cmd showReq cmd.Run 82 | &' 83 | ⍝ --- Setting a public field or property with _Set --- 84 | &' 85 | cmd.URL←baseURL,'_Set' ⍝ set a field or property 86 | cmd.Params←'{"instanceName":"',instance,'","what":"rates","value":[5,6,7]}' 87 | cmd showReq cmd.Run 88 | &' 89 | ⍝ --- Retrieving a public field or property with _Get --- 90 | &' 91 | cmd.URL←baseURL,'_Get' 92 | cmd.Params←'{"instanceName":"',instance,'","what":"terms"}' 93 | cmd showReq cmd.Run 94 | &' 95 | ⍝ --- Retrieving all public fields or properties with _Serialize --- 96 | &' 97 | cmd.URL←baseURL,'_Serialize' 98 | cmd.Params←'{"instanceName":"',instance,'"}' 99 | cmd showReq cmd.Run 100 | &' 101 | ⍝ --- Running a public method --- 102 | &' 103 | CodeLocation⍎instance,'.payments' ⍝ note - result is rank 3 which cannot be represented in JSON 104 | cmd.URL←baseURL,'_Run' 105 | cmd.Params←'{"instanceName":"',instance,'","methodName":"payments"}' 106 | cmd showReq cmd.Run 107 | &' 108 | srv.FlattenOutput←2 ⍝ flatten output and issue a warning message 109 | cmd showReq cmd.Run 110 | &' 111 | srv.FlattenOutput←1 ⍝ flatten output without warning message 112 | cmd showReq cmd.Run 113 | &' 114 | ⍝ --- _Run can be used to call any function, not just those in a class 115 | &' 116 | cmd.URL←baseURL,'_Run' 117 | cmd.Params←'{"methodName":"reverse","rarg":[2,4,6,8,10]}' 118 | cmd showReq cmd.Run 119 | &' 120 | ⍝ --- Passing arguments as data --- 121 | &' 122 | cmd.URL←baseURL,'loans/afford' ⍝ how much can I afford to borrow? 123 | cmd.Params←'[[5,6,7],[10,15],1000]' ⍝ interest rates, # years, desired maximum payment 124 | cmd showReq cmd.Run 125 | &' 126 | ⍝ --- Passing arguments as a namespace 127 | &' 128 | cmd.URL←baseURL,'loans/afford_ns' 129 | cmd.Params←'{"rates":[3,4,5],"terms":[10,20],"payments":[1000,1500]}' 130 | cmd showReq cmd.Run 131 | 132 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Tests/mixed/reverse.dyalog: -------------------------------------------------------------------------------- 1 |  r←reverse data 2 | r←⌽data 3 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /Tests/sessions/InitializeSession.dyalog: -------------------------------------------------------------------------------- 1 | InitializeSession req 2 | ⍝ initializes the session 3 | req.Session.Sum←0 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 | } -------------------------------------------------------------------------------- /Tests/setup.dyalog: -------------------------------------------------------------------------------- 1 |  r←setup dummy;home 2 | ⍝ Setup test 3 | ⎕IO←⎕ML←1 4 | r←'' 5 | :Trap (~##.halt)/0 6 | home←##.TESTSOURCE ⍝ hopefully good enough... 7 | {}#.⎕FIX 'file://',home,'../Source/JSONServer.dyalog' 8 | {}⎕SE.SALT.Load 'HttpCommand' 9 | :Else 10 | r←,⍕⎕DM 11 | :EndTrap 12 | -------------------------------------------------------------------------------- /Tests/teardown.dyalog: -------------------------------------------------------------------------------- 1 |  r←teardown dummy 2 | r←'' 3 | {}#.⎕EX'JSONServer' 'HttpCommand' 4 | -------------------------------------------------------------------------------- /Tests/test_KillOnDisconnect.dyalog: -------------------------------------------------------------------------------- 1 |  r←test_KillOnDisconnect;threads;j;h 2 | j←Jarvis.New'' 3 | ⎕FX'∇r←req killMe payload' 'req.KillOnDisconnect←1' 'r←⎕DL 30' '∇' 4 | ⎕FX'∇r←req doNotKillMe payload' 'r←⎕DL 30' '∇' 5 | j.Start 6 | h←HttpCommand.New'post' 7 | h.Params←'' 8 | h.BaseURL←'localhost:',⍕j.Port 9 | h.ContentType←'application/json' 10 | h.Timeout←5 11 | h.URL←'killMe' 12 | h.Run 13 | ⎕DL 5 14 | threads←≢⎕TNUMS 15 | h.URL←'doNotKillMe' 16 | h.Run 17 | ⎕DL 5 18 | threads,←≢⎕TNUMS 19 | j.Stop 20 | ⎕EX'killMe' 'doNotKillMe' 21 | r←(>/threads)/ 22 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Tests/testconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "Paradigm" : "JSON", 3 | "Port" : 9000 4 | } 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 -------------------------------------------------------------------------------- /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.20.3", 23 | } 24 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/assets/apl385.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/Jarvis/ca20c6d1d1b1404c1b65ba7fd8428723f23f19c5/docs/assets/apl385.ttf -------------------------------------------------------------------------------- /docs/concepts.md: -------------------------------------------------------------------------------- 1 | # Conceptual Overview 2 | ***Jarvis*** is implemented as a Dyalog APL class. It's okay if you're not familiar with the [object oriented features of Dyalog](https://docs.dyalog.com/latest/Dyalog%20Programming%20Reference%20Guide.pdf#%5B%7B%22num%22%3A846%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C0%2C714%2C0%5D). All you really need to understand is that a ***Jarvis*** server is an "instance" of the `Jarvis` class. You create an instance of the class and then configure and run the instance. This is covered in [Using Jarvis](using.md). 3 | 4 | ## Terminology 5 | ***Jarvis*** supports two different "paradigms" which define how the client will interact witht the ***Jarvis*** service. This section defines some terms that we will use in the discussion below about ***Jarvis*** paradigms. 6 | 7 | ### Client 8 | This is the component sending a request to the ***Jarvis*** service and receiving ***Jarvis***' response. The client can be anything that can send HTTP requests and receive HTTP responses. It could be a web browser, a program, an app on a mobile phone, another server, and so on. 9 | 10 | ### URL (or URI) 11 | The URL (Universal Resource Locator) or URI (Universal Resource Identifier) is the address, possibly including query parameters, which is sent to ***Jarvis***. A URL has the general format: 12 | ``` 13 | [scheme://][userinfo@]host[:port][/path][?query] 14 | ``` 15 | `scheme` is either `http` or `https`.
16 | `userinfo@` is HTTP Basic authentication user credentials if authentication is being used.
17 | `host` is the ***Jarvis*** server domain name or IP address.
18 | `port` is the optional port number. It defaults to 80 for `http` or 443 for `https`.
19 | `path` is the endpoint for the request.
20 | `query` are the query parameters, if any, for the request. 21 | 22 | ### HTTP method 23 | The HTTP protocol defines several request "methods" that a client may use to request the server to perform different operations. When you open a web browser on a URL, the browser is typically using the **GET** HTTP method to retrieve the requested content. Other HTTP methods include POST, PUT, DELETE, PATCH, and HEAD; each one designed to indicate the operation the server should perform. 24 | 25 | ### JSON 26 | JSON stands for **J**ava**S**cript **O**bject **N**otation and is a flexible notation for representing data arrays and objects. Dyalog APL has a system function, `⎕JSON`, which easily converts between JSON and APL arrays/namespaces. 27 | 28 | ### REST 29 | REST stands for **RE**presentational **S**tate **T**ransfer and is a design pattern for APIs. An API that follows this design pattern is termed "RESTful". When a RESTful API is called, the server will transfer to the client a representation of the state of the requested resource. 30 | 31 | ## Paradigms 32 | ***Jarvis*** supports two operational paradigms that we term **JSON** and **REST**. A ***Jarvis*** server can run only one paradigm at a time. One of the first decisions you'll need to make when using ***Jarvis*** is which web service paradigm to use. The paradigm will determine protocol for how a client will interact with ***Jarvis***. This section provides information to help you decide which paradigm is most appropriate for your application. 33 | 34 | ### The JSON paradigm 35 | The JSON paradigm may seem quite natural to the APLer in that the endpoints (functions) take a data argument and return a data result. The argument and result can be as complex as you like, provided that they can be represented using JSON. 36 | 37 | - The endpoints are the names of APL functions that are called to satisfy the request. You can specify which functions you want to expose as endpoints for your service to the client. 38 | - The client uses the HTTP POST method and passes the parameters for the request as JSON in the request body. 39 | - The payload (body) of the request is automatically converted by ***Jarvis*** from JSON to an APL array and passed as the right argument to your function. 40 | - Your function should return an APL array which ***Jarvis*** then converts to JSON and returns to the client in the response. 41 | - Your application needs to know nothing about JSON, HTTP or web services. 42 | 43 | ### The REST paradigm 44 | RESTful web services use standard HTTP methods to perform operations on resources. A resource (endpoint) is the `path` in the request's URL. The resource could be a physical resource like a file or a virtual resource that is constructed dynamically by your code. There are several other ["RESTful design"](https://en.wikipedia.org/wiki/Representational_state_transfer) principles or constraints, but they are beyond the scope of this document. 45 | 46 | - Operations are typically implemented corresponding to standard HTTP methods: 47 | - GET – read a resource 48 | - POST – create a resource 49 | - PUT – update/replace a resource 50 | - PATCH – update/modify a resource 51 | - DELETE – delete a resource 52 | - With ***Jarvis***, you specify which methods you want your service to support. 53 | - You then implement an APL function with the same name as each method. 54 | - Resources are specified in the `path` of the request URL. 55 | - Depending on how you design the service API, parameters, if any, can be passed in the URL, the query string, the body of the request, or some combination thereof. 56 | - The function you write is passed the request object and it's up to you to parse the URL, payload, headers, and query parameters to determine what to do and what the arguments are. 57 | - You decide on the content type and format of the payload of the response. Common response content types for RESTful web services are JSON, XML or HTML. 58 | - In general, the **JSON** paradigm is quicker and easier to implement, but a properly implemented **REST** paradigm 59 | 60 | ### JSON contrasted with REST 61 | In many cases, the same functionality can be implemented using either paradigm. 62 | 63 | With **JSON**, endpoints are the names of APL functions that you want to expose with your web service. You write one APL function per operation you want to perform. 64 | 65 | With **REST**, endpoints identify resources and the HTTP method determines the operation to perform on the resource. You write one APL function for each HTTP method your web service will support. 66 | 67 | To compare the two paradigms, let's imagine you want to retrieve the total of invoice 45 for customer 231. 68 | 69 | #### JSON Example 70 | One way to implement this using the JSON paradigm might be to: 71 | 72 | - specify that the client should provide the arguments as a JSON object with a "customer" element and an "invoicenum" element. For this example, it might look like 73 | {"customer":231,"invoice":45} 74 | - write an APL function called `GetInvoiceTotal` which would take a namespace as its argument. The namespace will contain elements named "customer" and "invoice" 75 | ``` 76 | ∇ namespace← GetInvoiceTotal namespace;costs 77 | [1] ⍝ the namespace argument was created by Jarvis from the JSON object in the request 78 | [2] costs←namespace.customer GetInvoiceItems namespace.invoice ⍝ retrieve the invoice item costs pseudo-code 79 | [3] namespace.total←+/costs ⍝ insert a total element into the namespace 80 | ∇ 81 | ``` 82 | - ***Jarvis*** will then convert the result, in this case the updated namespace, to JSON `{"customer":231,"invoicenum":45,"total":654.32}` and return it to the client 83 | 84 | #### REST Example 85 | 86 | Using the REST paradigm, you might specify a resource like `/customer/231/invoice/45/total`. Since this is a "read" operation, you would use the HTTP GET method to retrieve it. 87 | - You would write a function named `GET` (the same as the HTTP method) which would be passed the HTTP request object. The `Endpoint` element of the request object will be `'/customer/231/invoice/45/total'`. 88 | - Your function would need to parse the endpoint to determine what is being requested and then retrieve the information. 89 | - Your function would set the content-type for the response payload as well as format the retrieved information and assign it to the payload. 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /docs/docker.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/Jarvis/ca20c6d1d1b1404c1b65ba7fd8428723f23f19c5/docs/docker.md -------------------------------------------------------------------------------- /docs/img/dyalog-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 23 | 27 | 28 | 31 | 35 | 36 | 37 | 59 | 61 | 62 | 64 | image/svg+xml 65 | 67 | 68 | 69 | 70 | 71 | 77 | 80 | 83 | 88 | 89 | 92 | 97 | 98 | 101 | 106 | 107 | 110 | 115 | 116 | 119 | 124 | 125 | 128 | 133 | 134 | 135 | 136 | 142 | 146 | 150 | 155 | 156 | 160 | 165 | 166 | 170 | 175 | 176 | 180 | 185 | 186 | 191 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /docs/img/favicon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/Jarvis/ca20c6d1d1b1404c1b65ba7fd8428723f23f19c5/docs/img/favicon-32.png -------------------------------------------------------------------------------- /docs/img/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/Jarvis/ca20c6d1d1b1404c1b65ba7fd8428723f23f19c5/docs/img/sample.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | !!! note 2 | 3 | This documentation continues to be a work in progress. General usage and settings references are complete. Frequent updates will be forthcoming as additional sections are completed. 4 | 5 | **Jarvis** is an HTTP server that makes it easy to create a web service to provide access to your APL code from the web or a local network. 6 | 7 | Any client program written in any language on any platform that can process HTTP requests can access a Jarvis-based web service. This vastly increases the potential audience for your application - the client can be a standard web browser, a phone app, a browser-based app, or a custom client written in a language like Python or C# and yes, even APL. 8 | 9 | ## Introduction 10 | The name **Jarvis** is a pseudo-acronym for **J**SON **a**nd **R**EST Ser**vice** ("vice" becomes "vis") and was also inspired by J.A.R.V.I.S. (Just A Rather Very Intelligent System) from the [Marvel Cinematic Universe](https://en.wikipedia.org/wiki/J.A.R.V.I.S.). 11 | 12 | **Formatting Note:** You may notice that the name "Jarvis" is formatted in two, possibly three, different ways: 13 | 14 | * **Jarvis** refers Jarvis as an abstract idea 15 | * `Jarvis` refers to the Dyalog APL Jarvis class 16 | * Jarvis (unadorned) is probably the author's mistake for not having formatted the occurrence as one of the above. 17 | 18 | ## Design Goals 19 | **Jarvis** is designed to make it very easy for an APLer to create web services without requiring in-depth knowledge of web service frameworks. In designing **Jarvis** we've attempted to 20 | 21 | - Make **Jarvis**' default behavior simple and applicable to many use cases 22 | - Make few assumptions about what the user actually needs to do 23 | - Provide hooks to allow the user to tailor or extend **Jarvis**' behavior if needed 24 | 25 | ## Create an APL Web Service in 5 Minutes 26 | If you know how to write a monadic, result-returning APL function, you're ready to run your first **Jarvis**-based web service. Here's how: 27 | 28 | 1. If you already have a copy of the `Jarvis` class, skip to step 3. Otherwise, load the `HttpCommand` utility so that we can download a copy of **Jarvis** and also use `HttpCommand` for testing our web service. 29 | 30 | ]load HttpCommand 31 | 32 | 2. Next, download a copy of Jarvis. Note, the following statement downloads the latest, perhaps pre-release, version of the Jarvis class for this quick demonstration. For a production environment, you should use a [released version of Jarvis](https://github.com/Dyalog/Jarvis/releases). `HttpCommand.Fix` both downloads and runs `⎕FIX` on an APL code file from the web. 33 | 34 | HttpCommand.Fix 'https://raw.githubusercontent.com/Dyalog/Jarvis/master/Source/Jarvis.dyalog' 35 | 36 | 1. Write one or more monadic, result-returning APL functions. For instance: 37 | 38 | )cs # 39 | sum ← {+/⍵} ⍝ dfns work 40 | total ← +/ ⍝ derived functions work 41 | ⎕FX '∇r←addemup a' 'r←+/a' '∇' ⍝ and of course, tradfns work 42 | 43 | 1. Next, create an instance of `Jarvis` using `Jarvis.New`. 44 | 45 | j←Jarvis.New '' 46 | 47 | This will create a `Jarvis` instance with all settings set to their default values. By default, `Jarvis` will use port 8080 and look for your endpoint code in `#`. 48 | 49 | 1. You can now run your web service running on port 8080 and serving code from the # (root) namespace. 50 | 51 | ``` 52 | (rc msg)←j.Start 53 | 2024-09-06 @ 15.46.24.199 - Starting Jarvis 1.18.1 54 | 2024-09-06 @ 15.46.24.217 - Conga copied from C:\Program Files\Dyalog\Dyalog APL-64 19.0 Unicode/ws/conga 55 | 2024-09-06 @ 15.46.24.221 - Local Conga v3.5 reference is #.Jarvis.[LIB] 56 | 2024-09-06 @ 15.46.24.231 - Jarvis starting in "JSON" mode on port 8080 57 | 2024-09-06 @ 15.46.24.232 - Serving code in # 58 | 2024-09-06 @ 15.46.24.237 - Click http://192.168.001.123:8080 to access web interface 59 | ``` 60 | 61 | If the server started successfully, you'll see messages similar to those above displayed to the APL session and the return code `rc` should be `0` and `msg` should be empty. If there was any problem starting `Jarvis`, `rc` will be non-`0` and `msg` will contain a (hopefully) helpful message about the problem that occurred. 62 | 63 | 64 | Now, let's test our service using **Jarvis**' built-in HTML interface. You could click on the link displayed or open your favorite browser to http://localhost:8080, but just for fun, we'll use Dyalog's HTMLRenderer object. 65 | 66 | 'h' ⎕WC 'HTMLRenderer' ('URL' 'localhost:8080') 67 | ![Jarvis Sample](img/sample.png) 68 | 69 | We select the Endpoint (APL function) we want from the drop down list, enter some valid JSON data (`[1,3,5]`), and press Send to send the request to **Jarvis**. **Jarvis**' response is then sent back and displayed. 70 | 71 | We can also use `HttpCommand` to call the web service. 72 | 73 | (url data headers)←'localhost:8080/total' '[1,3,5]' ('content-type' 'application/json') 74 | (HttpCommand.Do 'POST' url data headers).Data 75 | 9 76 | 77 | We can use the cURL command to call the web service. 78 | 79 | C:\> curl -H "content-type: application/json" -X POST -d [1,3,5] http://localhost:8080/addemup 80 | 9 81 | 82 | To stop the service, simply type `j.Stop` 83 | 84 | Interested? Read on... 85 | -------------------------------------------------------------------------------- /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-operational.md#allowgets) setting will enable HTTP GET method to be used as well - by default `AllowGETs` is disabled. 20 | * The [`AllowFormData`](./settings-operational.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`](./reference.md#httprequest) 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) specified 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/methods-instance.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/Jarvis/ca20c6d1d1b1404c1b65ba7fd8428723f23f19c5/docs/methods-instance.md -------------------------------------------------------------------------------- /docs/methods-shared.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/Jarvis/ca20c6d1d1b1404c1b65ba7fd8428723f23f19c5/docs/methods-shared.md -------------------------------------------------------------------------------- /docs/reference.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/Jarvis/ca20c6d1d1b1404c1b65ba7fd8428723f23f19c5/docs/reference.md -------------------------------------------------------------------------------- /docs/release-notes.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/Jarvis/ca20c6d1d1b1404c1b65ba7fd8428723f23f19c5/docs/release-notes.md -------------------------------------------------------------------------------- /docs/request.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/Jarvis/ca20c6d1d1b1404c1b65ba7fd8428723f23f19c5/docs/request.md -------------------------------------------------------------------------------- /docs/rest.md: -------------------------------------------------------------------------------- 1 | **Jarvis**'s REST paradigm was developed to make it possible to deploy your APL application using a REST API. REST APIs are more applicable when managing a collection of resources. HTTP-based REST web services, like **Jarvis**, use standard HTTP methods (GET, POST, PUT, DELETE, etc) to create, retrieve, and manipulate resources. 2 | 3 | There are [six guiding principles of REST](https://en.wikipedia.org/wiki/REST#Architectural_constraints). The degree to which you adhere to these principles is completely up to you. 4 | 5 | ## How **Jarvis**'s REST mode works 6 | 7 | ### You write an APL function for each HTTP method your service will support 8 | Rather than writing a function for each endpoint as in the JSON paradigm, you will write a monadic function for each of the HTTP methods that your web service will support. Your functions should reside in the namespace specified by [`CodeLocation`](settings-operational.md#codelocation). 9 | 10 | You specify which HTTP methods your REST service will support using the [`RESTMethods`](settings-rest.md#restmethods) setting. For instance, setting `RESTMethods←'Get'` indicates that your service will support only HTTP GET requests. Such requests will call the `#.CodeLocation.Get` function, passing the HTTP request as its right argument. For the purposes of this document, we'll call the request right argument `Request`. 11 | 12 | Your `Get` function would then look at the resource being requested by parsing [`Request.Endpoint`](request.md#endpoint) element. If [`DefaultContentType`](settings-operational.md#defaultcontenttype) is set to `'application/json'` (the default), your function can return an APL array which `Jarvis` will convert to JSON. If you are not using `'application/json'`, then you will need to: 13 | 14 | 1. use [`Request.SetContentType`](request.md#contenttype) to set an appropriate content type 15 | 2. set the `Request.Response.Payload` to the content you want to send back to the client 16 | 17 | ### The client sends a request 18 | 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 interact with a resource, the client should: 19 | 20 | * Specify the resource in the request URL 21 | * Specify the HTTP method appropriate to the operation being requested 22 | * If the request includes a payload, specify an appropriate `content-type` header 23 | 24 | !!! Example 25 | 26 | To retrieve a hypothetical list of orders for customer with id 123, one might make a request like: 27 | `resp←HttpCommand.Get 'http://localhost:8080/customers/123/orders'` 28 | 29 | ### `Jarvis` receives the request 30 | 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. 31 | 32 | ### `Jarvis` calls your method function 33 | `Jarvis` passes the [`Request`](./reference.md#request) object as the right argument to the function appropriate for the HTTP method being used. It is up to your function to parse `Request.Endpoint` to determine the resource being requested. As noted above, if the response payload's `content-type` is `'application/json'` your function can return an APL array which `Jarvis` will automatically convert to JSON. Otherwise, your function is responsible for setting the `Request.Response.Payload` and `Request.ContentType` appropriately. 34 | 35 | If the requested resource is not found, or some other issue occurs, your function should fail the request with an appropriate HTTP status code using [`Request.Fail`](request.md#fail). For example, an HTTP status code of 404 means that the requested resource was not found and you would use `Request.Fall 404` to set the status code. 36 | 37 | !!! tip "Advanced Usage" 38 | **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 function. These are: 39 | 40 | * [`ValidateRequestFn`](./settings-hooks.md#validaterequestfn) specifies the name of a function to call for every request that `Jarvis` receives. 41 | * [`AuthenticateFn`](./settings-hooks.md#authenticatefn) specified the name of a function to call to perform authentication. 42 | 43 | ### `Jarvis` sends the response to the client 44 | `Jarvis` will format a proper HTTP response and send it to the client. -------------------------------------------------------------------------------- /docs/security.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/Jarvis/ca20c6d1d1b1404c1b65ba7fd8428723f23f19c5/docs/security.md -------------------------------------------------------------------------------- /docs/sessions.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dyalog/Jarvis/ca20c6d1d1b1404c1b65ba7fd8428723f23f19c5/docs/sessions.md -------------------------------------------------------------------------------- /docs/settings-conga.md: -------------------------------------------------------------------------------- 1 | **Jarvis** uses Conga for TCP/IP communications. `Jarvis`'s Conga settings are used to configure the Conga's operation. See the [Conga User Guide](https://docs.dyalog.com/latest/Conga%20User%20Guide.pdf) for more detailed information on specific settings. 2 | 3 | Once started, `Jarvis` maintains a reference to to Conga library in the `LDRC` field. This enables you to manage query and/or manage Conga settings directly if you need to. For instance, `j.LDRC.Tree '.'` will return the entire Conga object tree. 4 | 5 | ### `AcceptFrom` 6 | |--|--| 7 | |Description|`AcceptFrom` allows you to limit `Jarvis` incoming connections to a specific set of IP address ranges. `AcceptFrom` is either one or two character vectors that specify IPV4 and/or IPV6 address ranges. Each vector is a comma-delimited set of IP ranges. | 8 | |Default|`''`| 9 | |Examples|`j.AcceptFrom←'192.168.1.1/127,10.17.221.67/75'` | 10 | |Notes|This setting is documented as `AllowEndPoints` in the [Conga User Guide](https://docs.dyalog.com/latest/Conga%20User%20Guide.pdf). Unlike `AllowEndPoints`, you do not need to specify `'IPV4'` or `'IPV6'` as `Jarvis` can automatically determine which IP version is intended.| 11 | 12 | ### `BufferSize` 13 | |--|--| 14 | |Description|`BufferSize` specifies the maximum HTTP headers length that `Jarvis` will accept. The intent is to block malicious requests that attempt to overwhelm the server by sending huge requests.| 15 | |Default|`10000`| 16 | |Examples|`j.BufferSize←5000 ⍝ allow up to 5000 bytes of HTTP header data` | 17 | |Notes|`BufferSize` can be used in conjunction with [`DOSLimit`](#doslimit) to mitigate Denial of Service (DOS) attacks.| 18 | 19 | ### `DenyFrom` 20 | |--|--| 21 | |Description|Similar to [`AcceptFrom`](#acceptfrom), `DenyFrom` allows you to deny incoming connections from a specific set of IP address ranges. `DenyFrom` is either one or two character vectors that specify IPV4 and/or IPV6 address ranges. Each vector is a comma-delimited set of IP ranges.| 22 | |Default|`''`| 23 | |Examples|`j.DenyFrom←'192.168.1.1/127,10.17.221.67/75'`| 24 | |Notes|This setting is documented as `DenyEndPoints` in the [Conga User Guide](https://docs.dyalog.com/latest/Conga%20User%20Guide.pdf). Unlike `DenyEndPoints`, you do not need to specify `'IPV4'` or `'IPV6'` as `Jarvis` can automatically determine which IP version is intended.| 25 | 26 | ### `DOSLimit` 27 | |--|--| 28 | |Description|To reduce possible Denial Of Service (DOS) attacks, `DOSLimit` is used to limit the size of HTTP payloads that `Jarvis` will accept. | 29 | |Default|`¯1` which indicates to use the Conga default value of 10485760| 30 | |Examples|`j.DOSLimit←100000 ⍝ assumes no message will exceed 100000 bytes`| 31 | |Notes|You should specify a `DOSLimit` large enough to accept the largest message you anticipate receiving.| 32 | 33 | ### `FIFO` 34 | |--|--| 35 | |Description|`FIFO` controls how Conga will process incoming requests. Setting `FIFO` to `1` will cause Conga to process requests in a "First In, First Out" order. Setting `FIFO` to `0` will cause Conga to process requests according to Conga's `ReadyStrategy` setting.| 36 | |Default|`1`| 37 | |Examples|`j.FIFO←0 ⍝ turn FIFO mode off`| 38 | |Notes|This setting is documented as `EnableFifo` in the [Conga User Guide](https://docs.dyalog.com/latest/Conga%20User%20Guide.pdf).| 39 | 40 | ### `Port` 41 | |--|--| 42 | |Description|`Port` is port number that `Jarvis` will listen on.| 43 | |Default|`8080`| 44 | |Examples|`j.Port←22361`| 45 | |Notes|Allocating ports below 1024 on Linux typically requires root privileges due to security reasons.| 46 | 47 | ### `RootCertDir` 48 | |--|--| 49 | |Description|When running `Jarvis` over HTTPS, `RootCertDir` is the path to a folder containing public root certificates.| 50 | |Default|`''`| 51 | |Examples|Set `RootCertDir` to the public root certificates folder installed with Dyalog.
`dir←1 1⊃1 ⎕NPARTS 2 ⎕NQ '.' 'GetCommandLineArgs'`
`j.RootCertDir←dir,'PublicCACerts'`| 52 | |Notes|This setting is documented as `RootCertDir` in the [Conga User Guide](https://docs.dyalog.com/latest/Conga%20User%20Guide.pdf). See [`Security`](./security.md) for more information.| 53 | 54 | ### `Priority` 55 | |--|--| 56 | |Description|`Priority` is the [GnuTLS priority string](https://www.gnutls.org/manual/gnutls.html#Priority-Strings) when using secure communications. `Priority` specifies the TLS session's handshake algorithms when negotiating a secure connection.| 57 | |Default|'NORMAL:!CTYPE-OPENPGP'| 58 | |Examples|`j.Priority←'NORMAL:-MD5' ⍝ use the default without HMAC-MD5` | 59 | |Notes|Setting `Priority` to something other than the default requires an in-depth understanding of TLS session negotiation. Don't change it unless you know know what you're doing.| 60 | 61 | ### `Secure` 62 | |--|--| 63 | |Description|`Secure` is a Boolean setting that controls whether `Jarvis` will use TLS. Valid settings are:| 64 | |Default|`0`| 65 | |Examples|`j.Secure←1 ⍝ enable secure communications`| 66 | |Notes|Using TLS requires configuring several settings, see [Using TLS](./security.md#usingtls).| 67 | 68 | ### `ServerCertSKI` 69 | |--|--| 70 | |Description|Under Windows, when using the Microsoft Certificate Store to obtain the server certificate for `Jarvis` to use, `ServerCertSKI` is the **Serv**er **Cert**ificate **S**ubject **K**ey **I**dentifier of the certificate.| 71 | |Default|`''`| 72 | |Examples|`j.ServerCertSKI←'aca7d8f00691129ea0bc3613a00ed8ea9a5e55f5'`| 73 | |Notes|The subject key identifier is a 40 byte hexadecimal string. For more information, see [Using TLS](./security.md#usingtls).| 74 | 75 | ### `ServerCertFile` 76 | |--|--| 77 | |Description|`ServerCertFile` is the name of the file containing the server's public certificate.| 78 | |Default|`''`| 79 | |Examples|`j.ServerCertFile←'/etc/mycerts/publiccert.pem'`| 80 | |Notes|For more information, see [Using TLS](./security.md#usingtls).| 81 | 82 | ### `ServerKeyFile` 83 | |--|--| 84 | |Description|`ServerKeyFile` is the name of the file containing the server's private key.| 85 | |Default|`''`| 86 | |Examples|`j.ServerKeyFile←'/etc/mycerts/privatekey.pem'`| 87 | |Notes|**Never** share your private key file. For more information, see [Using TLS](./security.md#usingtls).| 88 | 89 | ### `ServerName` 90 | |--|--| 91 | |Description|`ServerName` is the Conga name for the `Jarvis` server. You can specify the name or have it Conga assign it.| 92 | |Default|Assigned by Conga in the format `'SRVnnnnnnnn'` where `nnnnnnnn` begins at `00000000` and is incremented when a `Jarvis` server using the same Conga instance is started.| 93 | |Examples|      ` )copy conga Conga`
      ` j1←Jarvis.New 8080`
      ` j2←Jarvis.New 8081`
      ` j3←Jarvis.New 8082`
      ` j3.ServerName←'MyJarvis'`
      ` (j1 j2 j3).Start`
      ` (j1 j2 j3).ServerName`
` SRV00000000 SRV00000001 MyJarvis`| 94 | |Notes|`ServerName` can be useful when interacting with Conga, particularly when debugging. For example:
      ` j3.(LDRC.Describe ServerName)`
`0 MyJarvis Server Listen`
[`LDRC`](./settings-shared.md#ldrc) is `Jarvis`'s local reference to the Conga library.| 95 | 96 | ### `SSLValidation` 97 | |--|--| 98 | |Description|`SSLValidation` is employed as part of the certificate checking process and is more fully documented in the [Conga User Guide](https://docs.dyalog.com/latest/Conga%20User%20Guide.pdf).| 99 | |Default|`64` - request but do not require a client certificate| 100 | |Examples|`j.SSLValidation←128 ⍝ require a valid certificate`| 101 | |Notes|For more information, see [Using TLS](./security.md#usingtls).| 102 | 103 | ### `WaitTimeout` 104 | |--|--| 105 | |Description|`WaitTimeout` is the number of milliseconds that `Jarvis` will wait in its listening loop before timing out.| 106 | |Default|`15000` - 15 seconds| 107 | |Examples|`j.WaitTimeout←60000 ⍝ wait a minute before timing out`| 108 | |Notes|Conga servers sit in a "wait" loop listening for communications from clients. If no communications occur before `WaitTimeout` has passed, Conga will signal a "Timeout" event and reiterate the "wait" loop.| -------------------------------------------------------------------------------- /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.| -------------------------------------------------------------------------------- /docs/settings-cors.md: -------------------------------------------------------------------------------- 1 | CORS, or Cross-Origin Resource Sharing, is a security feature implemented by web browsers to control how resources on a web page can be requested from another domain outside the domain from which the resource originated. It helps prevent malicious websites from accessing sensitive data on other websites. 2 | 3 | When a web page makes a request to a different domain (cross-origin request), the browser checks if the server allows such requests by looking at the CORS headers in the server's response. If the headers indicate that the request is allowed, the browser permits the resource to be accessed; otherwise, it blocks the request. 4 | 5 | CORS is essential for enabling secure communication between different web applications and APIs while protecting users from potential security risks. 6 | 7 | Without modification, **Jarvis**'s default CORS settings will allow most CORS requests. For more information about CORS, see [CORS](https://developer.mozilla.org/docs/Web/HTTP/CORS). 8 | 9 | ### `EnableCORS` 10 | |--|--| 11 | |Description|`EnableCORS` is a Boolean setting which enables or disables CORS support in `Jarvis`. Valid values are:| 12 | |Default|`1`| 13 | |Examples|`j.EnableCORS←0 ⍝ disable CORS support`| 14 | 15 | ### `CORS_Origin` 16 | |--|--| 17 | |Description|`CORS_Origin` specifies the domains from which requests are allowed. Valid values are:| 18 | |Default|`'*'` - requests from all domains are allowed.| 19 | |Examples|`j.CORS_Origin←'https://foo.example' ⍝ allow requests only from https://foo.example` | 20 | 21 | ### `CORS_Methods` 22 | |--|--| 23 | |Description|`CORS_Methods` controls which HTTP methods that will be allowed in cross-origin requests. Valid values are:| 24 | |Default|`¯1`| 25 | |Examples|`j.CORS_Methods←'GET,POST'`| 26 | |Notes|This setting applies only to [preflighted requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#preflighted_requests). You may also directly set the `Access-Control-Allow-Methods` header, which will override the `CORS_Methods` setting.| 27 | 28 | ### `CORS_Headers` 29 | |--|--| 30 | |Description|`CORS_Headers` controls what additional headers will be allowed in a CORS request. Valid values are: is a comma-delimited string specifies what additional HTTP response headers will be exposed | 31 | |Default|`'*'`| 32 | |Examples|`j.CORS_Headers←'X-Custom-Header'`| 33 | |Notes|By default, only the [CORS-safelisted response headers](https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_response_header) are exposed to the client. This setting applies only to [preflighted requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#preflighted_requests). You may also directly set the `Access-Control-Allow-Headers` header, which will override the `CORS_Headers` setting.| 34 | 35 | ### `CORS_MaxAge` 36 | |--|--| 37 | |Description|`CORS_MaxAge` indicates, in seconds, how long the results of a preflight request can be cached.| 38 | |Default|`60`| 39 | |Examples|`j.CORS_MaxAge←600 ⍝ set to 10 minutes (600 seconds)`| 40 | |Notes|This setting applies only to [preflighted requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#preflighted_requests). You may also directly set the `Access-Control-Max-Age` header, which will override the `CORS_MaxAge` setting.| -------------------------------------------------------------------------------- /docs/settings-hooks.md: -------------------------------------------------------------------------------- 1 | "Hook" functions are functions you write to inject custom behavior at specific points in `Jarvis`'s execution. You then assign the name of the function to the appropriate "hook". Hook functions can be located in `#.CodeLocation` - `Jarvis` will exclude them from being considered as endpoint functions. By default, no hook functions are defined; you need to define only the hook functions, if any, that are needed for your web service. 2 | 3 | ### `AppCloseFn` 4 | |--|--| 5 | |Description|`AppCloseFn` is the name of the niladic, function to be called when `Jarvis` is stopped. This function could do things like closing database connections, managing log files, etc. The function may return a 2-element array of `(rc msg)` where `rc` is an integer return code (`0` means "okay") and `msg` is a character vector message. If the function does not return a result, the `Jarvis` will return the return code and message that was set prior to calling `AppCloseFn`.| 6 | |Default|`''`| 7 | |Examples|`j.AppCloseFn←'ShutDown'`| 8 | 9 | ### `AppInitFn` 10 | |--|--| 11 | |Description|`AppInitFn` is the name of the monadic, result-returning function to be called when `Jarvis` is started. This function is called before `Jarvis` starts listening for requests. This function could do things like establishing database connections or other application initialization. The function should return a 2-element array of `(rc msg)` where `rc` is an integer return code (`0` means "okay") and `msg` is a character vector message. If the function is monadic, its right argument is a reference to the `Jarvis` instance.| 12 | |Default|`''`| 13 | |Examples|`j.AppInitFn←'Startup'`| 14 | |Notes|If your function returns a non-`0` return code, `Jarvis` will exit.| 15 | 16 | ### `AuthenticateFn` 17 | |--|--| 18 | |Description|`AuthenticateFn` is the name of monadic, result-returning function to be called when you need to authenticate a request. The right argument to the function is the [`Request`](./request.md) instance. The function result should either be:| 19 | |Default|`''`| 20 | |Examples|`j.AuthenticateFn←'Authenticate'`| 21 | |Notes|See [Authentication](./security.md#authentication) for more information about how to authenticate an HTTP request.| 22 | 23 | ### `PostProcessFn` 24 | |--|--| 25 | |Description|`PostProcessFn` is the name of monadic, non-result-returning function to be called *after* your endpoint has run but *before* the response is sent to the client. The right argument to the function is the [`Request`](./request.md) instance. The function should not return a result however, if it does, that result is ignored.
If you have some treatment that you need to apply to every response, `PostProcessFn` can be used to avoid having to add that treatment to every endpoint.| 26 | |Default|`''`| 27 | |Examples|`j.PostProcessFn←'PostProcess'`

   `∇ PostProcess req`
`[1] 'custom-header'req.SetHeader'some value' ⍝ add a custom header`
`[2] req.Reponse.Payload.Message←'Have a nice day!' ⍝ modify the payload`
   `∇`| 28 | 29 | 30 | 31 | ### `SessionInitFn` 32 | |--|--| 33 | |Description|`SessionInitFn` is the name of a monadic, result-returning function that can perform session initialization if your web service is using sessions. The right argument is the [`Request`](./request.md) instance, which we'll call `req`. The reference to the session namespace is `req.Session`. The integer function result should be either:| 34 | |Default|`''`| 35 | |Examples|`j.SessionInitFn←'InitSession`| 36 | |Notes|See [Using Sessions](./sessions.md) for more information.| 37 | 38 | ### `ValidateRequestFn` 39 | |--|--| 40 | |Description|`ValidateRequestFn` is the name of a monadic, result-returning function that will be called for every request that `Jarvis` receives. `ValidateRequestFn` gives you the opportunity to perform additional validation on a request. The right argument is the [`Request`](./request.md) instance. The function result should either be:| 41 | |Default|`''`| 42 | |Examples|`j.ValidateRequestFn←'Validate'`| 43 | |Notes|See [Validation](./security.md#validation) for more information.| -------------------------------------------------------------------------------- /docs/settings-json.md: -------------------------------------------------------------------------------- 1 | These settings apply when using **Jarvis**'s JSON paradigm. 2 | 3 | ### `AllowFormData` 4 | |--|--| 5 | |Description|`AllowFormData` controls whether `Jarvis` will accept requests with a content-type of `'multipart/form-data'`. This makes it more convenient when using a form in a web browser as the client or to upload a file. Valid settings are: