Question Dmitry Baranov · Apr 27, 2023

IRIS/REST, how could I get all HTTP headers?

It is clear how to get the request header value with a specific name:

Class My.RestController Extends %CSP.REST
{

ClassMethod processRequest() As %Status
{
    #dim request as %CSP.Request 
    set request = %request
    set h = ##class(%REST.Impl).%GetHeader("...")
    ...    
    return $$$OK
}

But I can't figure out how to enumerate ALL the request headers (and also get all values).

Product version: IRIS 2023.1

Comments

Yaron Munz · Apr 28, 2023

all headers are stored in the %request.CgiEnvs("HTTP"_xxxxx)

You may do something like this in the OnPreHTTP method in your CSP page:

Kill ^yourGlobal
S a="" F { S a=$O(%request.CgiEnvs(a)) Q:a=""  If $E(a,1,4)="HTTP" Set ^yourGlobal(a)=%request.CgiEnvs(a) }
0
Eduard Lebedyuk · Apr 29, 2023

Check this article.

One of the most simple options - a CSP utility method which outputs all objects as a response. Just add this to any part of your code:

set %response.ContentType = "html"
do ##class(%CSP.Utils).DisplayAllObjects()
return $$$OK
0
Alex Baumberg  Apr 30, 2023 to Eduard Lebedyuk

Thanks for the hint with the DispayAllObjects method but I see that it malforms the names of the original HTTP headers. Thus, the "User-Agent" header becomes "HTTP_USER_AGENT" and so on.

Let me clarify - I'm also trying to implement a kind of proxy REST service which accepts an original web request and forwards that request to some another REST service behind, keeping all the original HTTP headers. Perhaps there is some other way to do it?

0
Tani Frankel  May 2, 2023 to Alex Baumberg

Hi @Alex Baumberg !

First, if you just want to send an HTTP Request from one point to another you can use the HTTP Passthrough mechanism.

And see also my comment to Dmitrii's reply below.

In addition if you are focusing on FHIR requests (as I assume you are, per prior knowledge 😉) you can consider using the FHIR Interoperability Adapter mechanism where you will have a "digested" request which already includes all the HTTP Headers in the AdditionalInfo array.

0
Dmitrii Baranov · Apr 30, 2023

Finally, I've implemented the following brute solution. I don't like the look of this at all, but it works

ClassMethod test() As %Status
{
  #dim request as %CSP.Request
  set request = %request

  #dim headerName as %String
  set headerName = ""
  set headerName = request.NextCgiEnv(headerName)
  while (headerName '= "") {
    if $find(headerName, "HTTP_") '= 0 {
      set localHeaderName = $replace(headerName, "HTTP_", "")
      set localHeaderName = $replace(localHeaderName, "_", "- ")
      set localHeaderName = $zconvert(localHeaderName, "L")
      set localHeaderName = $zconvert(localHeaderName, "W")
      set localHeaderName = $replace(localHeaderName, "- ", "-")
      write localHeaderName, ": ", request.GetCgiEnv(headerName), !
    }
    set headerName = request.NextCgiEnv(headerName)
    }
    return $$$OK
}

0
Tani Frankel  May 2, 2023 to Dmitrii Baranov

Hi @Dmitrii Baranov 
You can take a peak at a similar task performed internally in the FHIR Server REST handler class -

HS.FHIRServer.RestHandler:marshallRequestFromHttp()

// For compatability, copy all HTTP_ headers into the AdditionalInfo section of the requestSet tKey = ""For {
        Set tKey = $ORDER(%request.CgiEnvs(tKey))
        Quit:tKey=""If tKey?1"HTTP_"1.E {
            // Determine the proper header name (will be all caps unfortunately)Set tHeader = $PIECE(tKey,"HTTP_",2,*) // Copy the HTTP headers - except for certain ones.If (tHeader '= "AUTHORIZATION") {
                Do pRequest.AdditionalInfo.SetAt(%request.CgiEnvs(tKey), "HEADER:"_tHeader)
            }
        }
    }

Note this is internal code.

You can also similar code for the Generic HTTP Service (used by the Passthrough I mentioned to Alex above), from:

EnsLib.HTTP.Service:addAttributesToBody()

Set tattrH=$O(%request.CgiEnvs("HTTP_"))
    While $E(tattrH,1,5)="HTTP_" {
        If tattrH'="HTTP_URL",tattrH'="HTTP_VERSION" {
            Set attr=$REPLACE($E(tattrH,6,*),"_","-"), lwrattr=$ZCVT(attr,"L")
            If '((lwrattr="transfer-encoding")&&($ZCVT(%request.CgiEnvs(tattrH),"L")="chunked")),'((lwrattr="content-encoding")&&($ZCVT(%request.CgiEnvs(tattrH),"L")="gzip")) {
                Set:..#TOLOWERHEADERVARS attr=lwrattr
                Set:'$D(lwrattrs(lwrattr)) pStream.Attributes(attr)=%request.CgiEnvs(tattrH), lwrattrs(lwrattr)=""Set:"content-length"=lwrattr tLen=pStream.Attributes(attr)
                Set:"content-type"=lwrattr tContentType=pStream.Attributes(attr)
            } ElseIf (lwrattr="content-encoding") {
                Set tgzip = 1ElseIf tLen = "x" {
                Set tLen="xchunked"
            }
        }
        Set tattrH=$O(%request.CgiEnvs(tattrH))
    }

Again this is internal code.

But coming back to my comment to Alex above, I would recommend using one of the approaches I mentioned there (the Generic Passthrough service, or the FHIR Interop. built-in service) and this way all of the above is already handled for you.

0
Dmitry Baranov  May 2, 2023 to Tani Frankel

Hi Tani, and thank you (again) for your valuable tips! The generic passthrough is an option.

0