Author Info
Chris Malek

Chris Malek is a PeopleToolsยฎ Technical Consultant with two decades of experience working on PeopleSoft enterprise software projects. He is available for consulting engagements.

About Chris Work with Chris
Looking for pain-free PeopleSoft web services? 😀
PeopleSoft Simple Web Services (SWS)

Introducing a small but powerful PeopleSoft bolt-on that makes web services very easy. If you have a SQL statement, you can turn that into a web service in PeopleSoft in a few minutes.

Contents

Controlling HTTP Status Codes

In REST, PeopleTools gives you some but not all control over the HTTP Status codes. In the REST paradigm, the status codes should be meaningful. Unfortunately, the PeopleTools REST does not give you complete control over the status codes. There is much left to be desired in this corner of the framework. You have to make some compromises on what you can return and the actual structure of your code. You will see many instance of this tag throughout this section Death by a thousand cuts! ๐Ÿ’€.

If you need to have complete control over the status code, I would suggest that you use a custom response value or header. If you have a proxy server or API gateway in front of your integration broker, then you can use that to translate the status codes to the client.

There are a few different “classes” of HTTP response codes. Let’s briefly go over them and talk about what is supported in the framework. There is a great Wikipedia document on status code

HTTP Status Class Usage Supported in PeopleTools
1xx Information was received No ๐Ÿ˜ž
2xx Success Yes some values are supported ๐Ÿ˜€. Use the HTTPResponseCode property on your response message.
3xx Redirection No ๐Ÿ˜ž
4xx Client Error Some support but you have to “tie your code in knots” and be on a recent tools release. ๐Ÿ˜ฐ Use the OnErrorHttpResponseCode which is derived from the PS_PT:Integration:IRequestHandler interface.
5xx Server Error No support in the handler but PeopleTools may send 500 errors if certain errors occur prior to getting to your handler code. ๐Ÿ˜ž

We will look below at how to use the supported values and how you have to setup your service operation and structure your code.

Base Service Operation Overview

For the examples in this section, I have configured a very basic service operation with no parameters. It looks like this:

Base SO Setup

A few things to note:

  • There are no parameters
  • The default status code is 200.
  • There are no Optional HTTP status codes configured to begin with.
  • There is no fault type configured.
  • We have setup the operation to return JSON
    • We will NOT use document classes here. We use a non-rowset message object and generate all JSON in PeopleCode.
    • The sample response is below.
{
 "guid": "3761af35-d646-455d-8fb9-37ef3b908ed8"
} 

The base PeopleCode handler looks like the following.

import PS_PT:Integration:IRequestHandler;

class statusCodeExampe1 implements PS_PT:Integration:IRequestHandler
   method onRequest(&msRequest As Message) Returns Message;
   
end-class;

method onRequest
   /+ &msRequest as Message +/
   /+ Returns Message +/
   /+ Extends/implements PS_PT:Integration:IRequestHandler.OnRequest +/
   
   
   Local Message &response;
   &response = CreateMessage(@("Operation." | &msRequest.OperationName), %IntBroker_Response);
   
   Local JsonObject &rootJSON = CreateJsonObject();
   &rootJSON.AddProperty("guid", GetJavaClass("java.util.UUID").randomUUID().toString());
   
   If &response.SetContentString(&rootJSON.ToString()) Then
      
   End-If;
   Return &response;
   
end-method;

The HTTP signature to call this service is:

1
2
3
GET http://testib.cedarhillsgroup.com/PSIGW/RESTListeningConnector/PSFT_CS/CHG_STATUS_TEST.v1/test HTTP/1.1
Authorization: Basic UFM6SGVsbG8xMjM=
Accept: application/json
A response is going to look like the following. Pay attention to the status code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
HTTP/1.1 200 OK
Connection: close
Date: Thu, 07 Nov 2019 17:32:43 GMT
Content-Length: 73
Content-Type: application/json; encoding="UTF-8"
Content-Encoding: gzip

{

  "guid": "3761af35-d646-455d-8fb9-37ef3b908ed8"
} 

As we progress through this section, we will change the service operation as well as the handler to see how the HTTP status codes work.

2xx Examples

In order to support different 2xx status codes you have to “whitelist” the code on the service operation setup. You do this on the service operation by inserting rows into the “Optional Status Codes” subpage.

Optional Status Codes

You can insert values into the grid and PeopleTools limits the ones you can insert. You cannot set any response status code that is not whitelisted. If you try there will be a server failure. We will see this below.

Optional Status Codes

The only values supported are:

  • 200 OK
  • 202 Accepted
  • 203 Non-Authoritative Information
  • 204 No Content
  • 205 Reset Content
  • 206 Partial Content
  • 207 Multi-Status

What happens if I try to set a response code that is NOT whitelisted? Let’s find out. If we take all the setup we have in the base service operation above but just add one line to the PeopleCode handler to try to return 202.

The last 2 lines of the handler look like this now:

   /* New piece of code to setup response code */
   &response.HTTPResponseCode = 202;
   Return &response;
   
end-method;

The response that comes back from the handler is the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
HTTP/1.1 200 OK
Connection: close
Date: Thu, 07 Nov 2019 17:56:48 GMT
Content-Length: 248
Content-Type: text/html; charset=UTF-8
Content-Encoding: gzip

<HTML><HEAD><TITLE>RESTListeningConnector</TITLE></HEAD><BODY>Http Response Code 202 was not found
 on Service Operation Definition CHG_STATUS_TEST_GET. Message Type: Response. (2,1123) 
 CHG_BOOK_SAMPLES.handlers.statusCodeExampe1.OnExecute  
 Name:onRequest  PCPC:548  Statement:8</BODY></HTML> 

There are a few ugly things about this:

  • There was an actual error but HTTP/1.1 200 OK was returned. ๐Ÿ˜• This is not correct but we have zero control here.
    • I really expect a 5XX status code here because this is configuration on the server.
  • Our HTTP request (not shown) said it accepted and “wanted” JSON. However, HTML was returned.
  • The response body shows the issue that we tried to set a response code that was not setup.
    • Http Response Code 202 was not found on Service Operation Definition CHG_STATUS_TEST_GET

200 Response

If you noticed in the base service operation configuration example, we had setup the service operation with a 200 OK to be the default HTTP status code returned. Our handler ran to success and returned a JSON document. The framework returned 200 OK and our handler code did not have to do anything special in regard to the status code. The default was used.

This is how you will configure most of your service operations. For 200, you do not need to do anything in your handler code.

202 Response

Wikipedia has the following definition for 202.

This might be appropriate for web services where the handler kicked off a batch job that will process in the future or maybe stored some data in a staging table that will be processed later.

Above we tried to set the 202 response without actually whitelisting the value. We will now actually whitelist the 202 value and try to set it like we did above with &response.HTTPResponseCode = 202;

We add the white list.

Optional Status Codes

We set the response code:

&response.HTTPResponseCode = 202;

When we call the service we get back this response:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
HTTP/1.1 202 Accepted
Connection: close
Date: Thu, 07 Nov 2019 18:15:04 GMT
Content-Length: 73
Content-Type: application/json; encoding="UTF-8"
Content-Encoding: gzip

{
  "guid": "ad32e9e5-ce2f-4680-80b1-445ecfd808a4"
} 

203 Non-Authoritative Information

Wikipedia has the following definition for 203.

I have not seen a use case for this in a production PeopleSoft setup. If PeopleSoft is serving as a front end proxy for another service or batch process this might be useful.

Let’s whitelist the 203 code and change our handler.

Optional Status Codes

The handler code now becomes:

&response.HTTPResponseCode = 203;

The HTTP response is:

HTTP/1.1 203 Non-Authoritative Information
Connection: close
Date: Thu, 07 Nov 2019 18:23:34 GMT
Content-Length: 72
Content-Type: application/json; encoding="UTF-8"
Content-Encoding: gzip

{
  "guid": "0d6cf692-9b01-4272-92da-ca78547141a9"
} 

204 No Content

Wikipedia has the following definition for 204.

This is a status code that tells the client that there should be nothing in the the body to look at but everything else was good. This is generally more applicable to a POST request where a client may be posting something to the server and may not need a response back other than an acknowledgement that the payload was accepted.

In our code below we are going to use it in a GET situation which does not make complete sense but it will show some interesting points.

Let’s whitelist the 204 code and change our handler.

Optional Status Codes

The handler code now becomes:

&response.HTTPResponseCode = 204;

The handler code from the base example has not changed. We are still setting the JSON body. However, we see in the response below that the PeopleTools framework actually stripped out the body that our handler set. This is both expected and unexpected. I wanted to point this out as if you set this accidentally in your code and your client is expecting a response you could run into issues.

The HTTP response is:

HTTP/1.1 204 No Content
Connection: close
Date: Thu, 07 Nov 2019 18:27:08 GMT
Content-Type: application/json; encoding="UTF-8"
Content-Encoding: gzip

205 Reset Content

Wikipedia has the following definition for 205.

In our code below we are going to use it in a GET situation which does not make complete sense but it will show some interesting points.

Let’s whitelist the 205 code and change our handler.

Optional Status Codes

The handler code now becomes:

&response.HTTPResponseCode = 205;

The HTTP response is:

HTTP/1.1 205 Reset Content
Connection: close
Date: Thu, 07 Nov 2019 18:33:48 GMT
Content-Length: 73
Content-Type: application/json; encoding="UTF-8"
Content-Encoding: gzip

{
  "guid": "cda5ac28-f0f0-47a5-ba0e-7823ee18fdf0"
} 

You will see that we got the 205 response and the JSON body.

206 Partial Content

Wikipedia has the following definition for 206.

This is a good HTTP status code for a web service that supports pagination and the full data is not returned.

Let’s whitelist the 206 code and change our handler.

Optional Status Codes

The handler code now becomes:

&response.HTTPResponseCode = 206;

The HTTP response is:

HTTP/1.1 206 Partial Content
Connection: close
Date: Thu, 07 Nov 2019 18:37:51 GMT
Content-Length: 73
Content-Type: application/json; encoding="UTF-8"
Content-Encoding: gzip

{
  "guid": "ac3d9761-fafa-452d-b91b-2057cb3caa14"
} 

207 Multi-Status

Wikipedia has the following definition for 207.

I can see this being used in PeopleSoft when your handler class several different component interfaces and some of them may have been successful and other may have failed. You will have to return some encoded response with those status codes and the client will have to parse and take appropriate action.

Let’s whitelist the 207 code and change our handler.

Optional Status Codes

The handler code now becomes:

&response.HTTPResponseCode = 207;

The HTTP response is:

HTTP/1.1 207 Unknown
Connection: close
Date: Thu, 07 Nov 2019 18:38:43 GMT
Content-Length: 73
Content-Type: application/json; encoding="UTF-8"
Content-Encoding: gzip

{
  "guid": "b2903976-6c1e-458d-a532-dffae5cfa7d9"
} 

The interesting thing here is that the text string that PeopleTools returned was HTTP/1.1 207 Unknown instead of HTTP/1.1 207 Multi-Status

Welcome to PeopleTools! Death by a thousand cuts! ๐Ÿ’€

4xx Examples

The 4xx class of errors are a bit different than the 2xx class. This is where you have to tie yourself in knots a bit to support these 4xx errors. You cannot send back 4xx errors from the main handler. This is actually extremely limiting and very disappointing. As we saw in the sections above, you can only set values on the HTTPResponseCode response messages that are whitelisted and only 2xx codes are allowed on the service operation setup.

So how do you return 4xx errors? First we need to add a “Fault type” message using the button on the Service Operation Setup.

Optional Status Codes

We will configure the fault message to use our same “generic” message which is a non-rowset message because we want to control the output in the PeopleCode. Additionally, we set the status code to 400 and the content type to JSON.

Optional Status Codes

Now we need to make some changes to our handler. Specifically, we need to add an onError method that will actually handle any errors that are thrown. It is in this method where you can set 4xx class errors but you do it differently than you do the 2xx class errors. If you do not implement this method in your handler the framework will return an error for you and for a “robust” and well designed API running in production this is probably not what you want.

Let’s look at a fully expanded handler.

import PS_PT:Integration:IRequestHandler;

class statusCodeExampe1 implements PS_PT:Integration:IRequestHandler
   method onRequest(&msRequest As Message) Returns Message;
   method onError(&msRequest As Message) Returns string;
   property integer OnErrorHttpResponseCode;
   property string OnErrorContentType;
end-class;

method onRequest
   /+ &msRequest as Message +/
   /+ Returns Message +/
   /+ Extends/implements PS_PT:Integration:IRequestHandler.OnRequest +/
   
   
   Local Message &response;
   &response = CreateMessage(@("Operation." | &msRequest.OperationName), %IntBroker_Response);
   
   Local JsonObject &rootJSON = CreateJsonObject();
   &rootJSON.AddProperty("guid", GetJavaClass("java.util.UUID").randomUUID().toString());
   
   If &response.SetContentString(&rootJSON.ToString()) Then
      
   End-If;
   
   /* force an error - This will jump to the OnEror Method */
   throw CreateException( - 1, - 1, "fake error thrown");
   
   Return &response;
   
end-method;


method onError
   /+ &msRequest as Message +/
   /+ Returns String +/
   /+ Extends/implements PS_PT:Integration:IRequestHandler.OnError +/
   Local string &responseErrorString;
   
   Local Exception &exception = &msRequest.IBException;
   
   Local JsonObject &rootJSON = CreateJsonObject();
   &rootJSON.AddProperty("error", &exception.ToString());   
   &responseErrorString = &rootJSON.ToString();
   Return &responseErrorString;
end-method;

There is a subtle difference between the response signature for onRequest and the onError

  • onRequest - returns a message object.
    • You can set more than just the content. I can set HTTP custom http headers.
  • onError - Returns a string.
    • You do not have access to the response message so there is no way to set HTTP custom headers. This is very limiting and frustrating. Death by a thousand cuts! ๐Ÿ’€

This is frustrating if you want to use the “magic” of PeopleSoft rowsets to return data. They do not work the same in the onError. You have to generally re-implement the encoding logic and return it as a string. Death by a thousand cuts! ๐Ÿ’€

If you look at all synchronous handlers you will see that they implement an interface. That is seen in this statement: implements PS_PT:Integration:IRequestHandler. If you open up that class you will see that methods and properties that are on the interface. There have been some small changes in recent tools release to support REST 4XX status codes. Specifically, these two properties were added to to interface.

  • property integer OnErrorHttpResponseCode; - You use this to return a whitelist 4xx class status code.
  • property string OnErrorContentType; - You use this to tell the IB framework what content encoding is in the response string of OnError. These have to be whitelisted as well.
Optional Status Codes

The thing that is not quite apparent is how errors in the OnRequest method get pushed to the onError method. Here is a screenshot that shows that any uncaught exception in the onRequest “jumps” to the onError. If you don’t implement the onError in your handler the system will return a response for you. However, you generally do not want that. I generally wrap 99% of the code in the onRequest with a try / catch block to avoid the onError method kicking in. However, if you want to support one of these supported 4xx status codes, then you have to return a response from the onError method and NOT your onRequest method. (You may want to read that a few times to make sure you understand it.)

Optional Status Codes

You have a limited set of 4xx errors that can be whitelisted.

Optional Status Codes

The only allowed 4xx class errors are the following.

  • 400 Bad Request
  • 403 Forbidden
  • 404 Not Found
  • 405 Method Not Allowed
  • 406 Not Acceptable
  • 409 Conflict
  • 411 Length Required
  • 412 Precondition Failed
  • 415 Unsupported Media Type
  • 417 Expectation Failed

We will go through each one below and see what they return and also find some issues.

400 Bad request

In the section directly above, we added a Fault type message and set the status code to 400 and the Content-Type to application/json. These are the default values. If we take that revised handler above that implements the OnError method and has the fake error embedded, we will see that we do not have to do anything special to get PeopleTools to return the default 400 status code.

The HTTP response is:

HTTP/1.1 400 Bad Request
Connection: close
Date: Thu, 07 Nov 2019 19:48:25 GMT
Content-Length: 152
Content-Type: application/json; encoding="UTF-8"
X-ORACLE-DMS-ECID: fc854e66-fb35-42cf-b0b2-0beabf4f0f46-00000029
Content-Encoding: gzip
X-ORACLE-DMS-RID: 0

{
  "error": "fake error thrown (-1,-1) CHG_BOOK_SAMPLES.handlers.statusCodeExampe1.OnExecute  Name:onRequest  PCPC:552  Statement:8 (-1,-1)"
} 

403 Forbidden

The 403 Forbidden error message might be a great place to actually return an error code when a user does not have access to the given data. This can be data security like row level security or maybe component interface security. They may have access to the web service but something they are requesting inside that service is not available to them.

I really wish you could return this inside the main onRequest handler. This is where I see we have to tie ourselves in knots or just be creative in how we structure out code (more on that in the sections below)

Let’s look at how we return the 403 error code.

We need to ensure we have whitelisted that code.

Optional Status Codes

Then we change our handler to set the 403 status code.

method onError
   /+ &msRequest as Message +/
   /+ Returns String +/
   /+ Extends/implements PS_PT:Integration:IRequestHandler.OnError +/
   Local string &responseErrorString;
   
   Local Exception &exception = &msRequest.IBException;
   
   Local JsonObject &rootJSON = CreateJsonObject();
   &rootJSON.AddProperty("error", &exception.ToString());
   
   &responseErrorString = &rootJSON.ToString();
   %This.OnErrorHttpResponseCode = 403;
   Return &responseErrorString;
end-method;

The response we get in this scenario is not what is expected. We set a JSON string have have JSON as the default error content type. However, we are getting and HTML response and the PeopleTools framework is escaping our JSON. Death by a thousand cuts! ๐Ÿ’€

1
2
3
4
5
6
7
8
9
HTTP/1.1 403 Forbidden
Connection: close
Date: Fri, 08 Nov 2019 05:09:24 GMT
Content-Length: 182
Content-Type: text/html; charset=UTF-8

{
    &quot;error&quot; : &quot;fake error thrown &#40;-1,-1&#41; CHG_BOOK_SAMPLES.handlers.statusCodeExampe1.OnExecute  Name:onRequest  PCPC:552  Statement:8 &#40;-1,-1&#41;&quot;
} 

This seems to be a bug with the handling of 403.

404 Not Found

The 404 Not Found is a great status when the client asks for a resource that is not there.

Let’s go ahead and just whitelist all the remaining 4xx code so we don’t have to do it below.

Optional Status Codes

Now if we change our line in the onError method to the following:

%This.OnErrorHttpResponseCode = 404;

Now we can test this and see if we have the same issues as the 404.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
HTTP/1.1 404 Not Found
Connection: close
Date: Fri, 08 Nov 2019 05:22:49 GMT
Content-Length: 152
Content-Type: application/json; encoding="UTF-8"
Content-Encoding: gzip

{
  "error": "fake error thrown (-1,-1) CHG_BOOK_SAMPLES.handlers.statusCodeExampe1.OnExecute  Name:onRequest  PCPC:552  Statement:8 (-1,-1)"
} 

We see above that the 404 worked as expected and JSON was returned.

405 Method Not Allowed

We will just test each of the following by just changing the value we set on the OnErrorHttpResponseCode property. In this case we set.

%This.OnErrorHttpResponseCode = 405;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
HTTP/1.1 405 Method Not Allowed
Connection: close
Date: Fri, 08 Nov 2019 05:25:17 GMT
Content-Length: 152
Content-Type: application/json; encoding="UTF-8"
Content-Encoding: gzip

{
  "error": "fake error thrown (-1,-1) CHG_BOOK_SAMPLES.handlers.statusCodeExampe1.OnExecute  Name:onRequest  PCPC:552  Statement:8 (-1,-1)"
} 

406 Not Acceptable

%This.OnErrorHttpResponseCode = 406;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
HTTP/1.1 406 Not Acceptable
Connection: close
Date: Fri, 08 Nov 2019 16:40:37 GMT
Content-Length: 152
Content-Type: application/json; encoding="UTF-8"
Content-Encoding: gzip

{
  "error": "fake error thrown (-1,-1) CHG_BOOK_SAMPLES.handlers.statusCodeExampe1.OnExecute  Name:onRequest  PCPC:408  Statement:6 (-1,-1)"
} 

409 Conflict

%This.OnErrorHttpResponseCode = 409;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
HTTP/1.1 409 Conflict
Connection: close
Date: Fri, 08 Nov 2019 05:26:43 GMT
Content-Length: 152
Content-Type: application/json; encoding="UTF-8"
Content-Encoding: gzip

{
  "error": "fake error thrown (-1,-1) CHG_BOOK_SAMPLES.handlers.statusCodeExampe1.OnExecute  Name:onRequest  PCPC:552  Statement:8 (-1,-1)"
} 

411 Length Required

%This.OnErrorHttpResponseCode = 411;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
HTTP/1.1 411 Length Required
Connection: close
Date: Fri, 08 Nov 2019 05:27:12 GMT
Content-Length: 152
Content-Type: application/json; encoding="UTF-8"
Content-Encoding: gzip

{
  "error": "fake error thrown (-1,-1) CHG_BOOK_SAMPLES.handlers.statusCodeExampe1.OnExecute  Name:onRequest  PCPC:552  Statement:8 (-1,-1)"
} 

412 Precondition Failed

%This.OnErrorHttpResponseCode = 412;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
HTTP/1.1 412 Precondition Failed
Connection: close
Date: Fri, 08 Nov 2019 05:27:33 GMT
Content-Length: 152
Content-Type: application/json; encoding="UTF-8"
Content-Encoding: gzip

{
  "error": "fake error thrown (-1,-1) CHG_BOOK_SAMPLES.handlers.statusCodeExampe1.OnExecute  Name:onRequest  PCPC:552  Statement:8 (-1,-1)"
} 

415 Unsupported Media Type

%This.OnErrorHttpResponseCode = 415;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
HTTP/1.1 415 Unsupported Media Type
Connection: close
Date: Fri, 08 Nov 2019 05:28:04 GMT
Content-Length: 152
Content-Type: application/json; encoding="UTF-8"
Content-Encoding: gzip

{
  "error": "fake error thrown (-1,-1) CHG_BOOK_SAMPLES.handlers.statusCodeExampe1.OnExecute  Name:onRequest  PCPC:552  Statement:8 (-1,-1)"
} 

417 Expectation Failed

%This.OnErrorHttpResponseCode = 417;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
HTTP/1.1 417 Unknown
Connection: close
Date: Fri, 08 Nov 2019 05:28:41 GMT
Content-Length: 152
Content-Type: application/json; encoding="UTF-8"
Content-Encoding: gzip

{
  "error": "fake error thrown (-1,-1) CHG_BOOK_SAMPLES.handlers.statusCodeExampe1.OnExecute  Name:onRequest  PCPC:552  Statement:8 (-1,-1)"
} 

OnError Code Structure

In the code examples so far, we were actually setting the HTTP response codes for the 4xx class and the response string in the onError method. However, this can be inconvenient. It probably makes more sense for you to set some class instance variables in your onRequest method which sets up a structured error response, sets the status code, and sets the response content type. You would think that we can do that since the OnErrorHttpResponseCode and OnErrorContentType properties exist on the class and can be set anywhere in the class. However, we will see below that it is not possible and the reason is obscured in the “blackbox” PeopleTools code. It seems properties and instance variables on your handler class are not accessible in the onError method (Death by a thousand cuts! ๐Ÿ’€) This seems like some sort of PeopleTools bug to me. We will look at a few examples below and recommend a structure to work within the issues in the framework.

First let’s try to do something rather sensible and make use of instance properties to set the JSON error message in the OnRequest method and also try to set the 404 status code. Then in the onError method we just reference that rootJSON property to return the JSON generated above. However, this does NOT work.

import PS_PT:Integration:IRequestHandler;

class statusCodeExampe1 implements PS_PT:Integration:IRequestHandler
   method onRequest(&msRequest As Message) Returns Message;
   method onError(&msRequest As Message) Returns string;
   property integer OnErrorHttpResponseCode;
   property string OnErrorContentType;
   property JsonObject rootJSON;
   
end-class;

method onRequest
   /+ &msRequest as Message +/
   /+ Returns Message +/
   /+ Extends/implements PS_PT:Integration:IRequestHandler.OnRequest +/
   
   Local Message &response;
   &response = CreateMessage(@("Operation." | &msRequest.OperationName), %IntBroker_Response);
   
   &rootJSON = CreateJsonObject();
   &rootJSON.AddProperty("error", "The object you request does not exists.");
   
   /* Fake a 404 Error */
   %This.OnErrorHttpResponseCode = 404;
   
   /* force an error - This will jump to the OnEror Method */
   throw CreateException( - 1, - 1, "fake error thrown");
   
   /* ------- */
   /* No code below here will run because of the throw above */
   &rootJSON.AddProperty("guid", GetJavaClass("java.util.UUID").randomUUID().toString());
   
   If &response.SetContentString(&rootJSON.ToString()) Then
      
   End-If;
   
   Return &response;
  
end-method;

method onError
   /+ &msRequest as Message +/
   /+ Returns String +/
   /+ Extends/implements PS_PT:Integration:IRequestHandler.OnError +/
  
   Return &rootJSON.ToString();
end-method;

When you try to run this code you get a 200 status code back and HTML. There is something strange here where the OnError did not accept the value we set with %This.OnErrorHttpResponseCode = 404. And the reference to the class property rootJSON did not seem to work either. This is very frustrating!

Death by a thousand cuts! ๐Ÿ’€
1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
Connection: close
Date: Fri, 08 Nov 2019 05:56:00 GMT
Content-Length: 185
Content-Type: text/html; charset=UTF-8
Content-Encoding: gzip

<HTML><HEAD><TITLE>RESTListeningConnector</TITLE></HEAD><BODY>fake error thrown (-1,-1) CHG_BOOK_SAMPLES.handlers.statusCodeExampe1.OnExecute  Name:onRequest  PCPC:480  Statement:7</BODY></HTML> 

Next we need to shift things around a bit and try to reverse engineer what is happening and how we can get this to work. We need some way of setting some variables that can be picked up in the onError method. The onRequest method code probably knows best if a 404 Not Found or 403 Forbidden should be given. We need to restructure out code a bit to make this work. This is not ideal but you have to work within the bound of the framework, warts and all.

Here is the second attempt. This actually still does NOT work as we will see. Death by a thousand cuts! ๐Ÿ’€

import PS_PT:Integration:IRequestHandler;

class statusCodeExampe1 implements PS_PT:Integration:IRequestHandler
   method onRequest(&msRequest As Message) Returns Message;
   method onError(&msRequest As Message) Returns string;
   property integer OnErrorHttpResponseCode;
   property string OnErrorContentType;
   property JsonObject rootJSON;
   
private
   instance integer &ResponseStatusCode;
   instance string &errorMessage;
end-class;

method onRequest
   /+ &msRequest as Message +/
   /+ Returns Message +/
   /+ Extends/implements PS_PT:Integration:IRequestHandler.OnRequest +/
   
   Local Message &response;
   &response = CreateMessage(@("Operation." | &msRequest.OperationName), %IntBroker_Response);
   &rootJSON = CreateJsonObject();
   
   /* Fake a 404 Error */
   &ResponseStatusCode = 404;
   &errorMessage = "The resource you requested does not exist.";
   
   /* force an error - This will jump to the OnEror Method */
   throw CreateException( - 1, - 1, "fake error thrown");
   
   /* ------- */
   /* No code below here will run because of the throw above */
   &rootJSON.AddProperty("guid", GetJavaClass("java.util.UUID").randomUUID().toString());
   
   If &response.SetContentString(&rootJSON.ToString()) Then
      
   End-If;
   
   Return &response;
   
end-method;

method onError
   /+ &msRequest as Message +/
   /+ Returns String +/
   /+ Extends/implements PS_PT:Integration:IRequestHandler.OnError +/
   
   
   %This.OnErrorHttpResponseCode = &ResponseStatusCode;
   Local JsonObject &rootJSONError = CreateJsonObject();
   &rootJSONError.AddProperty("error", &errorMessage);
   
   Return &rootJSONError.ToString()
end-method;
  • Lines 10 - 12: We are adding in some instance variables that should in theory be accessible to all methods.
  • Lines 25-26: We set the instances variables.
  • Line 29: Issue a throw statement to cause the onError to start.
  • Line 49: - Set the OnErrorHttpResponseCode property using the instance variable set on the onRequest
  • Line 50: - Create a new JSON Object
  • Line 51: - Add an “error” property to the JSON with the value of the &errorMessage instance variable.

When you run this, any reasonable developer would expect that this would return a 404 status with a JSON error of The resource you requested does not exist.. However, you would be very wrong to assume this.

What we actually get back below is:

  • Status = 400
  • error JSON property is blank.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
HTTP/1.1 400 Bad Request
Connection: close
Date: Fri, 08 Nov 2019 16:58:48 GMT
Content-Length: 38
Content-Type: application/json; encoding="UTF-8"
Content-Encoding: gzip

{
  "error": ""
} 

What does this tell us? Either that:

  • The instance variables of our PS_PT:Integration:IRequestHandler implementation are NOT accessible.
  • The instance variables were reset when the onError was triggered.
    • In some further testing NOT shown here, I found that instances variables are accessible but they are set to the value set in the constructor of your handler. This really looks like some kind of tools bug to me.

This is again very lame and it is endless issues like this where I can see a developer giving up and going back to a flat file integration. However, since we are writing a book about web services we must press on.

Let’s try to change the instance variables to properties instead. The only thing we change is lines 10 and 11.

import PS_PT:Integration:IRequestHandler;

class statusCodeExampe1 implements PS_PT:Integration:IRequestHandler
   method onRequest(&msRequest As Message) Returns Message;
   method onError(&msRequest As Message) Returns string;
   property integer OnErrorHttpResponseCode;
   property string OnErrorContentType;
   property JsonObject rootJSON;
   
   property integer ResponseStatusCode;
   property string errorMessage;
end-class;

method onRequest
   /+ &msRequest as Message +/
   /+ Returns Message +/
   /+ Extends/implements PS_PT:Integration:IRequestHandler.OnRequest +/
   
   Local Message &response;
   &response = CreateMessage(@("Operation." | &msRequest.OperationName), %IntBroker_Response);
   &rootJSON = CreateJsonObject();
   
   /* Fake a 404 Error */
   &ResponseStatusCode = 404;
   &errorMessage = "The resource you requested does not exist.";
   
   /* force an error - This will jump to the OnEror Method */
   throw CreateException( - 1, - 1, "fake error thrown");
   
   /* ------- */
   /* No code below here will run because of the throw above */
   &rootJSON.AddProperty("guid", GetJavaClass("java.util.UUID").randomUUID().toString());
   
   If &response.SetContentString(&rootJSON.ToString()) Then
      
   End-If;
   
   Return &response;
   
end-method;

method onError
   /+ &msRequest as Message +/
   /+ Returns String +/
   /+ Extends/implements PS_PT:Integration:IRequestHandler.OnError +/
   
   
   %This.OnErrorHttpResponseCode = &ResponseStatusCode;
   Local JsonObject &rootJSONError = CreateJsonObject();
   &rootJSONError.AddProperty("error", &errorMessage);
   
   Return &rootJSONError.ToString()
end-method;

The response is the same with using properties and instance variables. (Bang your head against the wall a few times.) This is very frustrating ๐Ÿ˜ .

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
HTTP/1.1 400 Bad Request
Connection: close
Date: Fri, 08 Nov 2019 17:05:18 GMT
Content-Length: 38
Content-Type: application/json; encoding="UTF-8"
Content-Encoding: gzip

{
  "error": ""
} 

Global variable may work but I try to avoid those as all costs as they can have very unexpected consequences. Let’s see if we can somehow use data on the Exception that we throw.

Let’s revise our handler to pass the HTTP Status code as the message number and embed the error message in the text. Of course, we could define a real message catalog for this but I want to make this example simple. We make a few small changes below:

import PS_PT:Integration:IRequestHandler;

class statusCodeExampe1 implements PS_PT:Integration:IRequestHandler
   method onRequest(&msRequest As Message) Returns Message;
   method onError(&msRequest As Message) Returns string;
   property integer OnErrorHttpResponseCode;
   property string OnErrorContentType;
   property JsonObject rootJSON;
   
private
   instance integer &ResponseStatusCode;
   instance string &errorMessage;
end-class;

method onRequest
   /+ &msRequest as Message +/
   /+ Returns Message +/
   /+ Extends/implements PS_PT:Integration:IRequestHandler.OnRequest +/
   
   Local Message &response;
   &response = CreateMessage(@("Operation." | &msRequest.OperationName), %IntBroker_Response);
   &rootJSON = CreateJsonObject();
   
   /* Fake a 404 Error */
   &ResponseStatusCode = 404;
   &errorMessage = "The resource you requested does not exist.";
   
   /* force an error - This will jump to the OnEror Method */
   throw CreateException(99999, &ResponseStatusCode, &errorMessage);
   
   /* ------- */
   /* No code below here will run because of the throw above */
   &rootJSON.AddProperty("guid", GetJavaClass("java.util.UUID").randomUUID().toString());
   
   If &response.SetContentString(&rootJSON.ToString()) Then
      
   End-If;
   
   Return &response;
   
end-method;

method onError
   /+ &msRequest as Message +/
   /+ Returns String +/
   /+ Extends/implements PS_PT:Integration:IRequestHandler.OnError +/
  
   Local Exception &exception = &msRequest.IBException;
   
   %This.OnErrorHttpResponseCode = &exception.MessageNumber;

   Local JsonObject &rootJSONError = CreateJsonObject();
   &rootJSONError.AddProperty("error", &exception.ToString( False));
   
   Return &rootJSONError.ToString()
end-method;

The response is what we expected. This makes sense that it would work because we are setting response properties in the onError method and we are using data that is “given” by the exception object.

You will notice that there is some additional information contained in the error message that has the context of where the error was thrown. This would make no sense to the client and should not be included. This was supposed to be removed by passing false to this statement &exception.ToString( False) according to PeopleBooks. However, that seems like another bug. Death by a thousand cuts! ๐Ÿ’€

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
HTTP/1.1 404 Not Found
Connection: close
Date: Fri, 08 Nov 2019 17:40:55 GMT
Content-Length: 167
Content-Type: application/json; encoding="UTF-8"
Content-Encoding: gzip

{
  "error": "The resource you requested does not exist. (99999,404) CHG_BOOK_SAMPLES.handlers.statusCodeExampe1.OnExecute  Name:onRequest  PCPC:360  Statement:7 (99999,404)"
} 

This is not ideal but I could see where you build up your onError method to have some logic on what status codes to return based on the &exception.MessageNumber. Additionally, if you wanted more control over the output message you may need to inspect the exception object and re-run it through your own message catalog retrieval. We will see a rough example of this below.

First let’s create a message catalog to represent our 404 error.

Message Catalog

We are going to make some major changes to our handler.

  • We are going to introduce some coupling between the message catalog number used in the exception object and the status code returned.
  • We are going to re-run our message catalog retrieval based on metadata in the exception object so we can remove the message set number and the context information. This is just an example implementation that needs to be extended.

Let’s look at what we are changing:

  • Line 24: We are now throwing an error that is tied to the 32100, 1 message catalog. We are also passing some arbitrary substitution variable that would really be a variable in the real-world.
  • Line 44: In the onError, we pull out the passed in exception object.
  • Line 48: We concatenate the message set and number together so we can use it in our logic.
  • Line 51: We have logic that ties the message set number to the status code.
  • Line 62: We call a custom method that will “cleanup” the exception message. This is optional.
  • Line 70: We implement a new method that will parse through our exception class and produce a “cleaner” error message by invoking the message catalog retrieval functions slightly differently than the delivered functionality.
import PS_PT:Integration:IRequestHandler;

class statusCodeExampe1 implements PS_PT:Integration:IRequestHandler
   method onRequest(&msRequest As Message) Returns Message;
   method onError(&msRequest As Message) Returns string;
   property integer OnErrorHttpResponseCode;
   property string OnErrorContentType;
   property JsonObject rootJSON;
   
private
   method extractCleanerExceptionMessage(&inException As Exception) Returns string;
end-class;

method onRequest
   /+ &msRequest as Message +/
   /+ Returns Message +/
   /+ Extends/implements PS_PT:Integration:IRequestHandler.OnRequest +/
   
   Local Message &response;
   &response = CreateMessage(@("Operation." | &msRequest.OperationName), %IntBroker_Response);
   &rootJSON = CreateJsonObject();
   
   /* force an error - This will jump to the OnEror Method */
   throw CreateException(32100, 1, "", "some-key-value");
   
   /* ------- */
   /* No code below here will run because of the throw above */
   &rootJSON.AddProperty("guid", GetJavaClass("java.util.UUID").randomUUID().toString());
   
   If &response.SetContentString(&rootJSON.ToString()) Then
      
   End-If;
   
   Return &response;
   
end-method;

method onError
   /+ &msRequest as Message +/
   /+ Returns String +/
   /+ Extends/implements PS_PT:Integration:IRequestHandler.OnError +/
   
   
   Local Exception &exception = &msRequest.IBException;
   
   
   Local string &messageSetConcat;
   &messageSetConcat = &exception.MessageSetNumber | "-" | &exception.MessageNumber;
   
   Evaluate &messageSetConcat
   When = "32100-1"
      %This.OnErrorHttpResponseCode = 404;
      Break;
   When-Other
      
      /* DO NOTHING - TAKE DEFAULT */
      Break;
   End-Evaluate;
   
   
   
   Local string &errorMessage = %This.extractCleanerExceptionMessage(&exception);
   Local JsonObject &rootJSONError = CreateJsonObject();
   &rootJSONError.AddProperty("error", &errorMessage);
   
   Return &rootJSONError.ToString()
end-method;


method extractCleanerExceptionMessage
   /+ &inException as Exception +/
   /+ Returns String +/
   
   /* Re-run the MsgGetTex on the exception object so we have better control over the output */
   Local string &return;
   
   Evaluate &inException.SubstitutionCount
   When = 0
      &return = MsgGetText(&inException.MessageSetNumber, &inException.MessageNumber, "");
      Break;
   When = 1
      &return = MsgGetText(&inException.MessageSetNumber, &inException.MessageNumber, "", &inException.GetSubstitution(1));
      Break;
   When = 2
      &return = MsgGetText(&inException.MessageSetNumber, &inException.MessageNumber, "", &inException.GetSubstitution(1), &inException.GetSubstitution(1));
      Break;
   When = 3
      &return = MsgGetText(&inException.MessageSetNumber, &inException.MessageNumber, "", &inException.GetSubstitution(1), &inException.GetSubstitution(1));
      Break;
   When-Other
      &return = &inException.ToString( False);
      Break;
   End-Evaluate;
   
   Return &return;
end-method;

The response with these changes has our 404 status code. Additionally, the error message in the body is much cleaner. It should not really be this hard but that is the cards we have been dealt with the framework.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
HTTP/1.1 404 Not Found
Connection: close
Date: Fri, 08 Nov 2019 18:21:55 GMT
Content-Length: 96
Content-Type: application/json; encoding="UTF-8"
Content-Encoding: gzip

{
  "error": "You do not have access to the resource you requested: some-key-value"
} 

Summary

  • We saw that we do have some support for custom HTTP status codes.
  • Not all status codes are supported
  • 4xx status codes are poorly implemented in the framework and you have to tie yourself in a knot to get a production ready handler.
  • You cannot set HTTP headers from OnError (Or I have not figured out a trick to do so.)