Question
· Sep 4, 2019

Cachè as a reverse proxy

Hello, has anyone tried to use Caché as a reverse proxy ?

We are trying to embed a dashboard server (Plotly Dash in this case, but it could be anything which runs on its application server) inside our application which is written in Caché.  
The dashboard/report server runs locally (for example, or inside a LAN) on port 8080, and has no authentication features, so we have to implement them on a different layer, and we'd like to use Caché for it.

We'd like to hide the dashboard server (port 8080 not exposed), and use it behind Caché, this way (putting it as simple as possible):


Caché should pass all the requests from the iframe only if the user logged into Caché has permission to access the information, and send back the HTML to the page.

Is it feasible ?  Has anyone tried to do it ?

I'm trying to test if it can be done looking at the REST broker class sources (I know, it's totally different purpose) , because it's the only example that I know that catches any url extension (not only .csp, .cls, etc) and I don't know what kind of urls the report server uses, it could be just an example to start from.

It could be useful as a pattern for us, since we often have to embed external components inside our GUI,

but I'm wondering if it's totally stupid or not.

TIA

Max

Discussion (5)3
Log in or sign up to continue

I don't think it's totally stupid. We're looking at doing something similar with another technology (JReport).

Here's a code sample to help you get started - just change the port / page / filters / permission checks appropriately.

Class Demo.CSPProxy Extends %CSP.Page
{

Parameter HOST = "127.0.0.1";

Parameter PORT = 8888;

Parameter PAGE = "jinfonet/runReport.jsp";

/// Event handler for <b>PreHTTP</b> event: this is invoked before
/// the HTTP headers for a CSP page have been sent.  All changes to the
/// <class>%CSP.Response</class> class, such as adding cookies, HTTP headers,
/// setting the content type etc. must be made from within the OnPreHTTP() method.
/// Also changes to the state of the CSP application such as changing
/// %session.EndSession or %session.AppTimeout must be made within the OnPreHTTP() method.
/// It is prefered that changes to %session.Preserve are also made in the OnPreHTTP() method
/// as this is more efficient, although it is supported in any section of the page.
/// Return <b>0</b> to prevent <method>OnPage</method> from being called.
ClassMethod OnPreHTTP() As %Boolean [ ServerOnly = 1 ]
{
    Set request = ##class(%Net.HttpRequest).%New()
    Set request.Server = ..#HOST
    Set request.Port = ..#PORT
    
    // TODO: Add other stuff here, like authentication.
    
    Set page = $Piece(%request.CgiEnvs("REQUEST_URI"),"/"_$classname()_".cls/",2)
    If (page = "") {
        Set %base = $classname()_".cls/"_$Piece(..#PAGE,"/",1,*-1)_"/"
        
        // TODO: add query parameters from %request to the URL requested below.
        $$$ThrowOnError(request.Get(..#PAGE))
    } Else {
        Set fullPage = "http://"_..#HOST_":"_..#PORT_"/"_page
        Do ##class(%Net.URLParser).Parse(fullPage,.parts)
        
        // TODO: Better way of checking the requested resource.
        If $Piece($Piece(parts("path"),"/",*),".",2) = "jsp" {
            Set %response.Status = ##class(%CSP.REST).#HTTP403FORBIDDEN
            Quit 0
        }
        $$$ThrowOnError(request.Send(%request.Method,page))
    }
    Set %data = request.HttpResponse.Data
        
    // TODO: Do any other headers matter?
        
    Set %response.Status = request.HttpResponse.StatusCode
    Set %response.ContentType = request.HttpResponse.ContentType
    Quit 1
}

/// Event handler for <b>PAGE</b> event: this is invoked in order to  
/// generate the content of a csp page.
ClassMethod OnPage() As %Status [ ServerOnly = 1 ]
{
    If (%response.ContentType [ "html") && $Data(%base) {
        &html<<base href="#(..EscapeHTML(%base))#">>
        Do %data.OutputToDevice()
    } Else {
        Do %data.OutputToDevice()
    }
    Quit $$$OK
}

}

Great sample to start, thanks a lot

The problem I'm having is that since the dashboard server is interactive,  it seems to call a lot of endpoints (e.g. http:/127.0.0.1:8080/foo/bar/(…)/anything ) which are not known in advance, so I need a way to route any suburl like http://myserver/csp/dashboards/* to 127.0.0.1:8080/*  , but also http://myserver/csp/dashboards/*/* to 127.0.0.1:8080/*/* etc.

I need to configure both the web server in order to route all these requests to the CSP gateway (not only *.csp, *.cls ...it could be *.py , who knows) and the CSP gateway should invoke a single class/page which should deliver them all, instead of having a page for each endpoint, which is how Caché handles them by default. For this reason I need to understand how the REST broker works. Maybe there are other examples of having one csp page serving different urls/endpoints, I don't know.

You could use Apache's mod_rewrite to take all requests that fall under a certain sub-path and transform them on the fly to point to your CSP page. It could add the information about which specific page/resource was requested as a URL parameter that could be accessed by your CSP page.

For example, if a client makes a request for:
http://myserver/reporting/Dashboard1/resource2.js

mod_rewrite could change it to:
http://myserver/csp/dashboards/proxy.csp?targetResource=Dashboard1/resou...

After mod_rewrite changes the URL in the request, Apache continues processing it as usual using the new URL. Since the new URL refers to a CSP page Apache will pass it to CSP Gateway as we want.

With correct web server configuration to route everything through the CSPGateway, the above example should handle URLs for other resources like that without issue (as long as the content served by the dashboard server has relative links to those endpoints, not absolute) - that's the point of the <base> element.

In my sample use case, it also handles several requests for images and other assets.

It is not a big problem to make it with Caché. But I would recommend you to look at different ways. Using Caché just as a reverse proxy, looks like a sledgehammer to crack a nut.

First of all, you should go from an original source of users, I don't see it in your question, but it can be LDAP/AD or something else outside of Caché. 

I would look at Nginx for example, which is actually very flexible.