How to handle multi-value headers in Javascript?

Updated with New Information!

The Javascript callout capability in Apigee Edge is super flexible, making it nice for doing just about anything to a request or response message, whether that is parsing it, extracting data, transforming it, searching, validating, setting default values, and so on. Just running through that list makes me think I could post a series of quick articles here on the community site, outlining how to do each of these things. If I were to do that, I think @Birute Awasthi here at Apigee would be very pleased (She's the community advocate). I'll make no commitments, because I have a day job. But I will write at least ONE article in the set, on this topic: handling multi-value headers.

RFC2616, the IETF RFC that defines the HTTP protocol says:

> Multiple message-header fields with the same field-name MAY be present in a message if and only if the entire field-value for that header field is defined as a comma-separated list [i.e., #(values)]. It MUST be possible to combine the multiple header fields into one "field-name: field-value" pair, without changing the semantics of the message, by appending each subsequent field-value to the first, each separated by a comma. The order in which header fields with the same field-name are received is therefore significant to the interpretation of the combined field value, and thus a proxy MUST NOT change the order of these field values when a message is forwarded.

Clearly, it is legal for an HTTP request or response to have multiple values for a single header, or multiple instances of the same "header key". A good example might be, Cookie headers. A request may include multiple Cookie headers, something like:

 GET /alpha/beta?q=7
 Host: www.example.com
 Cookie: JSESSIONID=alphabeta120394049
 Cookie: AWSELBID=baaadbeef6767676767690220

And a response might include multiple Set-Cookies:

 HTTP/1.1 200 OK
 Server: Apigee-Edge
 Set-Cookie: JSESSIONID=alphabeta120394049; HttpOnly
 Set-Cookie: AWSELBID=baaadbeef6767676767690220; Path=/alpha

So how would a developer access those headers from within a Javascript callout? The "normal" way to access a single-valued header is:

var hdr = context.getVariable('request.header.host'), 
    ctype = context.getVariable('request.header.content-type);

Accessing a multi-valued header in the same way returns a Java ArrayList to the Javascript. (Remember, the JS callout is running inside the Rhino script engine, which itself is hosted on the Java VM. So the values returned from such calls as context.getVariable() are actually set by Java code).

According to the doc page on variables in Apigee Edge , multi-valued headers can be accessed with a .values suffix. In Javascript, this would look like this:

 var hdr = context.getVariable('response.header.set-cookie.values');

If you then try to request hdr.toString(), you may get this error:

{
  "fault": {
    "faultstring": "Execution of Javascript-ExtractResponseCookies failed with error: Javascript runtime error: \"Access to Java class \"java.util.ArrayList\" is prohibited. (extractResponseCookies_js#11)\"",
    "detail": {
      "errorcode": "steps.javascript.ScriptExecutionFailed"
    }
  }
}

VERY unfriendly. In my opinion, this shouldn't happen, and I'm petitioning the product gods to change this behavior.

The good news is there is a way to avoid this.

UPDATE, 2019 Feb 26.

If you want to get the raw, string value of a header, that may or may not have commas within it, then you can use this form:

 var hdr = context.getVariable('response.header.set-cookie.values.string');

Just append ".values.string" to the "request.header.HEADERNAME" . This works for any header, request or response. For example, suppose a request header called "foo" which has a value "a=bar,b-123". Retrieving "request.header.foo.values" gives you the ArrayList thing I described above. Retrieving "request.header.foo.values.string" gives you the original string value of the header, "a=bar,b=123".

This also works in Condition statements (outside of JavaScript):

   <Step>
     <Name>SC-CheckAuthorization</Name>
     <Condition>request.header.foo.values.string = "a=bar,b=123"</Condition>
   </Step>

This is the older workaround I described previously (Don't Use This, it's too complicated and has no advantage over the previous method):

var hdr = context.getVariable('response.header.set-cookie.values')+'';

This coerces the value, which is an arraylist, to a string. The result looks something like "[value1, value2, value3]", so you need to deal with those square brackets if you decide to parse the value. For example:

// get the array of header values: 
var a = hdr.substring(1, hdr.length-1).split(',');

Pretty simple, eh? Thanks to @Mike Dunker for the tip. To carry the example just a little further, if you want just the first portion of each cookie (not the HttpOnly, nor the Path, nor the Expires, etc), then:

for (var i=0, L = a.length, oneHeader; i<L; i++) {
  oneHeader = a[i].split(';')[0].trim();
}

Ok, I hope this was helpful. In my next installment I will outline how to automatically file Birute's nag emails into a special invisible email folder.

Comments
birute
Staff

@Dino definitely! and I think many people hanging out here would appreciate it.

Make us all proud 🙂

Not applicable

Thanks, @Dino. I've just wanted to let you know that I helped another developer with this solution.

API-Evangelist
Participant V

Thanks for the post..After extracting the values(var a),is there a way to assign var 'a' in JS to use it further?

DChiesa
Staff

Hi Vinay, I would be glad to help you. But please as a new, toplevel question? And be specific about what you are trying to accomplish. I don't know what it means to say "is there way to assign var 'a'?" a is already assigned. so you'll have to elaborate.

but not HERE. In a new question please.

API-Evangelist
Participant V
Not applicable

Is there a simi writeup for Java?

dchiesa1
Staff

not that I know of. It should work in a more straightforward way in Java because a Java callout will receive a Collection<String> when retrieving the context variable like "request.header.name-of-multi-valued-header". And within Java you will be able to manipulate (read/write) that collection directly.

In JavaScript, you cannot access the native Java collection object, so there's a need to transform it implicitly into a string, and perform string manipulation. In Java that's not necessary.

If you have tried something and it's not working, and you cannot figure out why, please post a new question (don't reply to this with comment with a new question), and I'll see if I can help you.

robert
Participant V

hey @Dino-at-Google. Just noticed a couple things and wanted to confirm provide some updated info.

1) it appears that the following recommendation from above does NOT work for query params. Also, this property is not in the "flow variables" docs for the headers or query params.

 var qp = context.getVariable('request.queryparam.myparam.values.string');

2) this doesn't work (as noted in your original post) and perhaps the documentation should be updated with the workaround (item 3 below) until it is fixed?

var qp = context.getVariable('request.queryparam.myparam.values')

I couldn't get the above to coerce to a string until I saw your post about adding + ''

3) this does work

var sort = context.getVariable('request.queryparam.myparam.values')+'';

Obviously these two are meant to work together for looping through the params. I was able to get it all working using the following:

var count = context.getVariable('request.queryparam.myparam.values.count');
var qp  = context.getVariable('request.queryparam.myparam.values')+'';

//remove "[" and "]" from string and create the array
var params = qp.slice(1,-1).split(",");

//loop through the array
params.forEach(function(element, i) {
    //your code here
    //element has the value        
}

It would be great if we could have the following root issue fixed in a future release!

var qp = context.getVariable('request.queryparam.myparam.values')
Version history
Last update:
‎03-31-2015 08:18 AM
Updated by: