I'm trying to execute a simple REST call via the OpenEdge.Net.HTTP library classes. See code example below.
BLOCK-LEVEL ON ERROR UNDO, THROW. /* ********************************************************************* */ /* Call rest */ /* ********************************************************************* */ USING OpenEdge.Net.*. USING OpenEdge.Net.UriSchemeEnum. USING OpenEdge.Net.HTTP.*. USING Progress.Json.ObjectModel.*. /* ********************************************************************* */ /* Call rest */ /* ********************************************************************* */ DEFINE VARIABLE v_Client AS OpenEdge.Net.HTTP.IHttpClient NO-UNDO . DEFINE VARIABLE v_Request AS OpenEdge.Net.HTTP.IHttpRequest NO-UNDO . DEFINE VARIABLE v_Response AS OpenEdge.Net.HTTP.IHttpResponse NO-UNDO . DEFINE VARIABLE v_ReturnOffset AS INTEGER NO-UNDO INIT ?. MESSAGE "HERE {&FILE-NAME} {&LINE-NUMBER} START". PAUSE. /* ********************************************************************* */ /* Call rest */ /* ********************************************************************* */ define variable oURI as URI no-undo. oURI = new URI("http", "xyz.com", 8889). oURI:Path = "GetTimeZoneOffsetFromHpuxName/EST5EDT". v_Request = NEW OpenEdge.Net.HTTP.HttpRequest("GET", oURI). /*v_Request:Method = "GET".*/ MESSAGE "HERE {&FILE-NAME} {&LINE-NUMBER} FINISH (METHOD NOT SHOWN): " v_Request:Method. PAUSE.
This is only the beginning part, but I'm not even able to get this much to work right. At the point "METHOD NOT SHOWN", the method on the request is blank whereas it should be "GET". If I uncomment the line right before it, I do see that the method appears as "GET". I can't understand why the constructor isn't working properly.
My troubles are happening with 11.6 and 11.7. Is this OpenEdge.Net.HTTP stuff open source? The docs that I found our pretty basic and aren't helping me much. https://documentation.progress.com/output/oehttpclient/116/
I think the source code would help troubleshoot these types of issues a bit more.
I am able to use the "fluent builders" to get things working. So far those haven't failed me. Here is an example of that...
v_Request = RequestBuilder:Get("xxx:8889/.../EST5EDT")
:AcceptJson()
:Request.
But at a high level, I'm trying to use these classes to run a simple GET operation against a REST service in 5 ms. Unfortunately as-of now I cannot do better than 50-100 ms. I turned up 4GLTrace logging to level 3 and I get several thousand lines of output, most of which is related to the fluent builders that I'm trying to optimize away (without any luck). Here is the proc stats for a simple REST request.
Procedure call statistics: 12:02:47 Caller Callee Load Size Calls Rd Bytes Reread Time <top> OpenEdge.ApplicationServer 6157 1 0 0 0 ... app0000 OpenEdge.Core.Util 48408 1 0 0 0 app0000 OpenEdge.Net.HTTP 1303 2 354278 0 4 ClientBuilder OpenEdge.Core.Util 52706 3 52706 0 0 ClientBuilder OpenEdge.Net.HTTP 25197 4 27342 0 0 BuilderRegistry OpenEdge.Core 10530 1 70124 0 1 Assert OpenEdge.Core.Assertion 52706 1 0 0 0 DefaultHttpClientBuilder OpenEdge.Net.HTTP 4472 2 118041 0 2 ClientBuilder OpenEdge.Net.HTTP.Lib 3239 1 104398 0 1 ClientLibraryBuilder OpenEdge.Core.Util 23877 2 0 0 0 ClientLibraryBuilder OpenEdge.Net.HTTP 3079 2 0 0 0 ClientLibraryBuilder OpenEdge.Net.HTTP.Lib 25197 1 0 0 0 ClientLibraryBuilder OpenEdge.Net.HTTP.Lib.ABLSockets 2111 1 17609 0 2 ABLSocketLibraryBuilder OpenEdge.Net.ServerConnection 49849 2 0 0 0 ABLSocketLibraryBuilder OpenEdge.Net.HTTP.Lib.ABLSockets 26444 1 26444 0 0 HttpClient OpenEdge.Net.HTTP 1284 4 18928 0 2 RequestBuilder OpenEdge.Net 4329 1 4329 0 0 URI OpenEdge.Net 39945 1 80601 0 0 URI OpenEdge.Core.Collections 39945 2 0 0 0 RequestBuilder OpenEdge.Core.Util 23877 2 0 0 0 RequestBuilder OpenEdge.Net.HTTP 7645 4 28876 0 0 DefaultRequestBuilder OpenEdge.Net.HTTP 39819 2 0 0 1 HttpMessage OpenEdge.Core.Collections 39945 4 0 0 0 HttpMessage OpenEdge.Net.HTTP 19520 3 0 0 0 HttpRequest OpenEdge.Net.HTTP 6778 1 71020 0 10 HttpHeaderBuilder OpenEdge.Core.Util 23877 9 0 0 0 HttpHeaderBuilder OpenEdge.Net.HTTP 62435 26 90129 0 20 DefaultHeaderBuilder OpenEdge.Net.HTTP 34209 10 0 0 0 HttpMessage OpenEdge.Net.HTTP.Filter.Writer 9915 1 25683 0 3 SetHeaderMessageWriterBuilder OpenEdge.Core.Util 23877 1 0 0 0 SetHeaderMessageWriterBuilder OpenEdge.Net.HTTP 3079 1 0 0 0 ResponseBuilder OpenEdge.Core.Util 23877 2 0 0 0 ResponseBuilder OpenEdge.Net.HTTP 3079 3 0 0 1 DefaultResponseBuilder OpenEdge.Net.HTTP 39819 2 0 0 0 HttpClient OpenEdge.Core.System 19309 2 0 0 0 ABLSocketLibrary OpenEdge.Core 10132 1 83487 0 0 ByteBucket OpenEdge.Core 5771 5 21898 0 1 ABLSocketLibrary OpenEdge.Net.HTTP.Filter.Writer 11002 2 58602 0 1 RequestWriterBuilder OpenEdge.Core.Util 48408 2 0 0 0 RequestWriterBuilder OpenEdge.Net.HTTP 2321 2 0 0 0 RequestWriterBuilder OpenEdge.Net.HTTP.Filter.Writer 16127 1 0 0 0 RequestWriterBuilder OpenEdge.Net.HTTP.Filter.Payload 13736 2 0 0 0 ClientSocket OpenEdge.Core 7367 2 0 0 0 ClientSocket sockethelper 2729 1 2729 0 0 ByteBucket OpenEdge.Core.Util 5726 1 0 0 0 ABLSocketLibrary OpenEdge.Net.HTTP.Filter.Payload 37210 1 0 0 0 ClientSocket OpenEdge.Net.ServerConnection 5771 1 0 0 0 Memptr OpenEdge.Core 9091 1 9091 0 0 EntityWriterBuilder OpenEdge.Net.HTTP.Filter.Writer 1107 2 102109 0 0 EntityWriterRegistry OpenEdge.Core.Util 23877 1 0 0 0 EntityWriterRegistry OpenEdge.Net.HTTP 3079 1 0 0 0 MessageWriterBuilder OpenEdge.Core.Util 23877 2 0 0 0 MessageWriterBuilder OpenEdge.Net.HTTP 3079 2 0 0 0 MessageWriterBuilder OpenEdge.Net.HTTP.Filter.Writer 26444 2 0 0 2 DefaultMessageWriterBuilder OpenEdge.Net.HTTP.Filter.Payload 13736 2 0 0 0 HttpClient OpenEdge.Net.HTTP.Filter.Writer 16365 1 33649 0 4 StatusCodeWriterBuilder OpenEdge.Core.Util 23877 1 0 0 0 StatusCodeWriterBuilder OpenEdge.Net.HTTP 3079 1 0 0 0
I'm a bit discouraged that my HttpClient approach in OE won't ever run in less than 5 ms. Does anyone have experience making REST calls from ABL?
OK, Thanks for the feedback. I figured something like that was going on, which is why I was hoping to take a peek at the source. The API is fluent (which I really like, so long as it is fast). But of course one big problem with ABL is that every single method call has a measurable performance penalty.
Any chance of open-sourcing this stuff? I'd love to be able to see what the fluent "builders" do, so that I can pick-and-choose only the portions that I need.... It takes every trick in the book to squeeze performance out of ABL.
The Microsoft.Net-based approach described in the following KB ended up getting me down to about 3 ms round-trips on my REST client calls. (I will be using this as an optimization for WIN32 deployments). knowledgebase.progress.com/.../Is-there-an-HTTP-client-for-OpenEdge
I see a growing number of pl/OO libraries in recent versions of OpenEdge. How does Progress choose when to compile an API like this down to machine code (instead of leaving it as r-code)? I think there are some relatively new things they've added (eg DOM/SAX for XML) that are fast because they are built into the AVM itself. I don't see why HTTP stuff can't be done the same way...
If you want to optimise for Win32/64 you should look into creating your own implementation of an IHttpClientLibrary; that way you can keep the public/client-dev interfaces as-is and just swap out the implementations.
There are a couple of places where the HTTP client is slow/inefficient - one big one is in how we deal with the HTTP message bodies: since the ABL does not (yet) have a natively dynamically-expandable MEMPTR structure, this gets done in ABL which is quite a bit slower than it'd be in the AVM. That said, in 12.0 (for which there's a new ESAP "coming soon") we changed the implementation of the ABL ByteBucket to use a native structure (a Progress.IO.MemporyOutputStream). That will be faster, although I've not tested yet exactly how much faster.
Yes yes, the famous `initialization pattern`… there should be somewhere a factory that takes care of calling that `initialize` method for you or it might use the `lazy initialization` to be nice or just throw you a `non initialized` error right in your face when you try to use it without knowing how to do it :)