It's not so much important to have key before install, much more important to have it when server is running.

But how you sure that your key is suitable for this platform. You can check it on running container, where you can enter inside, and go the csession. You can find some intersting methods for $SYSTEM.License in the documentation which can help you to check license file inside the container.

Most of the reasons for getting such error is just missed license file or exceeded license limit.

Just check it, you can mount it during docker run or copy it inside the image during docker build.

I see that you use quite an old version, I would recommend considering usage latest version based on IRIS. Due to many limitations of using such an old version in Docker.

There is also ClassMethod GetGlobalSize in the class %Library.GlobalEdit , where you can select a fast way to count or not, and you will get a different result.

 ClassMethod GetGlobalSize(Directory As %String, GlobalName As %String, ByRef Allocated As %Integer, ByRef Used As %Integer, fast As %Boolean = 0) as %Status

Get size of this global
'Allocated' - total size, in MB, of blocks allocated for the global.
'Used' - total used data, in MB, for the global.
'fast' - TRUE : faster return, it won't return the value of 'Used'.
FALSE - slower return,, it returns values for both 'Allocated' and 'Used'.

So, when fast, it just counts blocks and don't care how those blocks fill by data and multiply the number of blocks on Size of the block.

Used, counts only when you pass fast=0, and it calculates exact size, and to be more accurate reads all blocks, so it could be slower.

Visual Studio and Visual Studio Code are two very different products but just with similar names.

To configure Visual Studio Code, you can use this settings

{
    "objectscript.conn": {
        "active": true,
        "host": "localhost",
        "port": 57772,
        "ns": "SAMPLES",
        "username": "admin",
        "password": "SYS"
    }
}

Where,

  • active, should be true, if you going to be connected
  • port should point to Web server port, and not super port
  • username/password, the user should have enough permissions with role %Development

If you would like to do it with %Installer Manifiest, it is not going to be easy, unfortunately. CSPApplication there quite limited.

I can't just do it this way.

<CSPApplication Url="${CSPApp}" Directory="${CSPAppDir}" iKnowEnabled="yes"/>

You should first create simple csp application

<CSPApplication Url="${CSPApp}" Directory="${CSPAppDir}"/>

Add something like this method

ClassMethod EnableiKnow(pCSPName As %String = "") As %Status
{
  new $namespace
  znspace "%SYS"
  set props("iKnowEnabled")=1
  d ##class(Security.Applications).Modify(pCSPName,.props)
  quit $$$OK
}

But unfortunately, I also can't just call it from an installer, Invoke tag should be placed in Namespace tag. which is actually current namespace, but you should compute it first. This may help to declare a couple more default variables. Just place two first lines before your Installer generator

  do %code.WriteLine($c(9)_"set pVars(""CURRENTCLASS"")="""_%classname_"""")
  do %code.WriteLine($c(9)_"set pVars(""CURRENTNS"")=$namespace")
  quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "setup")

And finally, you can add this to your installer. Place it after CSPApplication creation, but outside of Namespace tag.

<Namespace Name="${CURRENTNS}">
    <Invoke Class="${CURRENTCLASS}" Method="EnableiKnow" CheckStatus="true">
      <Arg Value="${CSPAPP}"/>
    </Invoke>
  </Namespace>

To activate OS authentication in your docker image, you can run this code, in %SYS namespace

Do ##class(Security.System).Get(,.p) Set p("AutheEnabled")=p("AutheEnabled")+16 Do ##class(Security.System).Modify(,.p)

If you work with community edition, you can use my image, where you can easily define also user and password for external use.

Running server

$ docker run -d --rm --name iris \
 -p 52773:52773 \
 -e IRIS_USER=test \
 -e IRIS_PASSWORD=test \
 daimor/intersystems-iris:2019.1.0S.111.0-community

Terminal connect

$ docker exec -it iris iris session iris

Node: 413a4da758e7, Instance: IRIS

USER>write $username
root
USER>write $roles
%All

Or with docker-compose, something like this

  iris:
    image: daimor/intersystems-iris:2019.1.0S.111.0-community
    ports:
      - 52773:52773
    environment:
      IRIS_USER: ${IRIS_PASSWORD:-test}
      IRIS_PASSWORD: ${IRIS_PASSWORD:-test}

You can extend UrlMap only by splitting routes

While in one class you have this

XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Url="/class/:namespace/:classname" Method="GET" Call="GetClass" Cors="true"/>
<Map Prefix="/something" Forward="Some.Your.Class"/>
</Routes>
}

You can add one more class, whic will work only with routes started with /something with

XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Url="/more" Method="GET" Call="More" Cors="true"/>
</Routes>
}

Where real route will be /something/more


  Set ht = ##class(%Net.HttpRequest).%New()
  Set ht.Server = "server"
  Do ht.SetParam("param1", "true")
  Do ht.SetHeader("myheader", "headervalue")
  Set tSC = ht.Get("/api/get", 1)

Look at the second parameter in the call of Get method, it is a test flag, helps to understand how your request will actually go

GET /api/get?param1=true HTTP/1.1
User-Agent: Mozilla/4.0 (compatible; InterSystems IRIS;)
Host: server
Accept-Encoding: gzip
myheader: headervalue

Look at this code, it will do the same


  Set ht = ##class(%Net.HttpRequest).%New()
  Set ht.Server = "server"
  Do ht.SetParam("param1", "true")
  Do ht.SetHeader("myheader", "headervalue")
  Set ht.Location = "/api/get"
  Set tSC = ht.Get(, 1)

Or even this code


  Set ht = ##class(%Net.HttpRequest).%New()
  Do ht.SetHeader("myheader", "headervalue")
  Set tSC = ht.Get("http://server/api/get?param1=true", 1)

And how it looks like just with telnet

$ telnet echo.websocket.org 80

Copy-Paste this data

GET / HTTP/1.1
Accept: */*
Host: echo.websocket.org
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: 7BOhi3I1WkBoazaXv+MfWA==
Sec-WebSocket-Version: 13

After the empty line, it will show response

HTTP/1.1 101 Web Socket Protocol Handshake
Connection: Upgrade
Date: Tue, 05 Feb 2019 10:37:17 GMT
Sec-WebSocket-Accept: /gSfI5y+P3MMhONARUXNHG5vrHc=
Server: Kaazing Gateway
Upgrade: websocket

You should not forget that WebSockets is still used the HTTP protocol, so, you should send some headers first.

Look at this my code, it uses plain OPEN, and I'm not sure if my example 100% correct, but works.

  set securityKey = $SYSTEM.Util.CreateGUID()
  set securityKey = $SYSTEM.Encryption.MD5Hash(securityKey)
  set securityKey = $SYSTEM.Encryption.Base64Encode(securityKey)

  set host = "echo.websocket.org"
  set url = "/"
  set port = 80

  set device = "|TCP|"_port
  Open device:(host:port:"SCWD"::8192:8192:/TCPNOXY)
  Use device

  Write "GET ",url," HTTP/1.1",!
  Write "Accept: */*",!
  Write "Host: ",host,!
  Write "Connection: Upgrade",!
  Write "Upgrade: websocket",!
  Write "Sec-WebSocket-Key: ",securityKey,!
  Write "Sec-WebSocket-Version: 13",!
  Write !,*-3
  
  Use device:(::"A":$char(13))
  Set fullResponse = ""
  Do {
    Set response = ""
    Read response:1
    Quit:'$test
    Set fullResponse = fullResponse_response_$char(13)
  } While $test

  Use 0
  Close device

  Write !!,fullResponse

In this case, it only reads the first response, which actually should be with HTTP headers as well. Something like this.

HTTP/1.1 101 Web Socket Protocol Handshake
Connection: Upgrade
Date: Tue, 05 Feb 2019 10:51:37 GMT
Sec-WebSocket-Accept: qU2IAmlBvnSoEctnti8lcbc4bVA=
Server: Kaazing Gateway
Upgrade: websocket

It does not contain the first portion of data, which some WebSocket servers may send after initial connect. But if your server sends it, you should see it at the and of response. If you have to send something before, you should do it after the first response, which says that connection established and you can send any data. But not any, it should be in binary format, more details you can find here. Any responses also decoded.

If you can edit this code, you can try change to this.

<Data name="DESCRIP_2"> <RetrievalCode> S {DESCRIP_2}=$P($G(^PHPROP({L1},"DESC_CODES")),"\",2) S {DESCRIP_2}=$S($L({DESCRIP_2}):$Get(^SEDMIHP($P({DESCRIP_2},","),$P({DESCRIP_2},",",2))),1:{DESCRIP_2}) S {DESCRIP_2}=$E({DESCRIP_2},1,80) </RetrievalCode> </Data>

But not sure, if this correct.

What I did there, is, wrapped retrieving data from global ^SEDMIHP with the function $Get()

Or this way, with the default value

<Data name="DESCRIP_2"> <RetrievalCode> S {DESCRIP_2}=$P($G(^PHPROP({L1},"DESC_CODES")),"\",2) S {DESCRIP_2}=$S($L({DESCRIP_2}):$Get(^SEDMIHP($P({DESCRIP_2},","),$P({DESCRIP_2},",",2)),{DESCRIP_2}),1:{DESCRIP_2}) S {DESCRIP_2}=$E({DESCRIP_2},1,80) </RetrievalCode> </Data>

Well, such an interesting topic, and also quite wide.

Ok, TWAIN, is an API for image sources, it can be scanner or photo camera. You just asking about working with TWAIN, without any explanation of how you are going to use it and what sort of devices you going to utilize.

So, I'll just share some of my experience. How I've used only scanners, different types and with different workflows.

  • Server way. One or more stream document scanner (sorry don't know right term in English). Just any scanner which can work in network and configured to place all scans to some network folder or send my emails. Before sending some documents to this scanner, the operator should stick some barcode, on the title page, or on added empty first page. On the server side, we have used ABBYY Recognition Server, which just watches some folder, and can decode barcode and recognize text in the document, pack it in XML and place it in another folder. This folder was watched by Ensemble, where we searched for barcode in our system and placed this as an attachment to this document. With recognized text, we also are able to search documents in our system by the content of this document.
  • Client way. The operator working with a personal scanner connected right to his machine. While our application web-based and the server is far from this scanner, we used only client resources to scan images. We used java-applet which worked directly with TWAIN sources, so operator just called some functionality right from our application after finishing the scan, it has appeared as an attachment in the document. But this case now has some issues due to limitations for JAVA plugins in modern browsers. But it is now possible to find some modern solutions which may help to get access to twain on modern browsers, you can just google it.

SSO, can be achieved in some different ways. It can work over OAuth2, NTLM, Kerberos, SAML and so on. In different projects, I have used Kerberos/NTLM and OAuth2. But real SSO was only with Kerberos. And when you already have LDAP Auth in your application, it will be quite easy to add SSO. But also depends on which OS and which WEB server you have. On Windows much easier to start with IIS while so difficult to find a working module for Apache. On Linux there is also could be a problem to find the latest version of the module which will work with the latest version of apache. But when you will manage to get it worked on web-server side, on Caché side, almost nothing to do left. When you get first unauthorized request, you should return back with status 401, and say which method of authentication you need through header WWW-Authenticate: NTLM. Then if web server managed to get username, it will send it by header REMOTE_USER. Of course, you will not get password, you just use this username and authorize session. 

If your server on windows, you have two connection options. 

  • Terminal. Available only locally, and can use windows security. Enabled by default.
  • Telnet. Used to connect from outside. Disabled by default (you can activate this service in SMP.). After enabling, you can connect using terminal or any other tool by default port 22.

If your server on Linux. You have only one option is csession or irissession tool which works only locally to the server. For remote access you should use ssh or telnet. But you can't configure telnet from Cache. You should do it by yourself.

How Caché works with licenses, actually very interesting and sometimes quite difficult to understand, but it is possible to find a balance for everybody. Fortunately, at the same time, it has actually some tricks on how to turn it on your side.

The first important thing is every time when user login, you should log in the user not only in security but license as well. In this case, if the user uses the same IP address will be used the license unit.

In your case, I'm not even sure about forcing log out, do you really need it? You mentioned that your application still web, and does not matter is at wrapped as an application or opened right from the browser. You should have the same behaviour. So, you can reduce timeout for the session, add some timer which will ping the server from time to time when app is active to extend session time. Sessions on the server side also have a grace period after a timeout which is 5 minutes long, in most cases enough to return back from the call.