kbmMW contains some nice features to get easy access to a headers and cookies. For example via attributes:
[kbmMW_Service('name:SMARTDEMO')] [kbmMW_Rest('path:/myserver')] TMyService = class(TkbmMWCustomHTTPSmartService) public [kbmMW_Rest('method:get, path: "echoheader", responseMimeType:"text/plain"')] function EchoHeader([kbmMW_Arg(mwatHeader,'Accept')] const AHeaderValue:string):string; [kbmMW_Rest('method:get, path: "echocookie", responseMimeType:"text/plain"')] function EchoCookie([kbmMW_Arg(mwatCookie,'MyCookie')] const ACookieValue:string):string; end; ... function TkbmMWCustomService2.EchoHeader(const AHeaderValue:string):string; begin Result:=AHeaderValue; end; function TkbmMWCustomService2.EchoCookie(const ACookieValue:string):string; begin Result:=ACookieValue; end;
In the above example, accessing the server from a HTTP client like this:
http://localhost:1111/myserver/echoheader
Will result in the return of the value of the Accept header, which in my case resulted in:
text/html,application/xhtml+xml,application/xml;q="0.9",image/webp,image/apng,*/*;q="0.8"
Similarly requesting echocookie will return the value of the cookie named MyCookie, that the browser may have sent to the server. In my case, because there is no cookie defined with that name in the browser, the result will be empty.
This way you can easily get access to numerous header and/or cookie values.
But in the cookie scenario, we did not find any cookie because none was defined. So how do we return a cookie to the web client, that it can send to us next time?
Lets modify the EchoCookie method to return the cookie it received from the web client (as before), but then explicitly either create a new cookie in the client or update its existing value.
function TMyService.EchoCookie(const ACookieValue:string):string; var hlp:TkbmMWHTTPTransportStreamHelper; cookie:TkbmMWHTTPCookie; begin Result:=ACookieValue; // Set and return a new cookievalue that basically contains current date time in ISO8601 format. hlp:=TkbmMWHTTPTransportStreamHelper(ResponseTransportStream.Helper); cookie:=TkbmMWHTTPCookie(hlp.Cookies.ValueByName['MyCookie']); if cookie=nil then cookie:=TkbmMWHTTPCookie(hlp.Cookies.New('MyCookie')); cookie.Value:=TkbmMWDateTime.Now.ISO8601String; cookie.Expires:=TkbmMWDateTime.Now.IncHour(1); // Expires one hour from now. end;
This example introduced two classes, TkbmMWHTTPTransportHelper, and TkbmMWHTTPCookie.
With each call to a method in a service, two transport streams are made available, a request transport stream (self.RequestTransportStream) which contains what we received from the caller augmented with various data the application server sees fit to augment with, and a response transport stream (self.ResponseTransportStream) which will contain the data we want to return to the caller.
Part of the response transport stream will be the result of this method (a string) along with the response MimeType we defined using the attributes. However there are many other header type values that can be returned, like cookies and such (when its a response for a HTTP type client).
Each transport stream has a helper attached, which makes certain specialized features easier available. When the transport stream format of the transport, has been set to REST (which it is in our case), we can rest assured that the helper must be a TkbmMWHTTPTransportHelper or a descendant of that. So casting it nonchalantly will work fine in this case, but if the EchoCookie has dual use, meaning it can be called from a non REST type client (which it could if we added the attribute [kbmMW_Method] in the interface section for that particular method, then we should actively test if the helper is indeed a TkbmMWHTTPTransportHelper before using it as such.
Thats not the case in our sample here so lets continue.
Now we have access to a helper object (hlp). In that helper object you will find loads of nice things, like access to the Cookies property, where you can get access to all cookies provided by the web client (browser) if you are looking via the RequestTransportStream’s helper, or access to all the cookies you want to return to the web client if you are looking via the ResponseTransportStream.
Thus we check if MyCookie exists. If it does not, then we create a new one which is automatically added. Afterwards we update its value and when the cookie expires.
So now when we first time call the echocookie REST call, we will again receive an empty result (still no cookies defined), but 2nd time we make the call, the ISO8601 timestamp of the first call is returned as the cookie value, and the cookie is then updated again and its expiration extended.
As you may have guessed by now, the hlp instance is also the entrance to accessing and manipulating header values.
Let us make a method that returns any header value for any header name we specify in the call:
[kbmMW_Rest('method:get, path: "echoanyheader/{AHeaderName}", responseMimeType:"text/plain"')] function EchoAnyHeader([kbmMW_Rest('value: "{AHeaderName}"')] const AHeaderName:string):string;
And its implementation:
function TMyService.EchoAnyHeader(const AHeaderName:string):string; var hlp:TkbmMWHTTPTransportStreamHelper; begin hlp:=TkbmMWHTTPTransportStreamHelper(RequestTransportStream.Helper); Result:=hlp.Header.ValueByName[AHeaderName]; end;
It again cast to get a specialized helper, but now on the RequestTransportStream, and then return the value of a specific header item.
http://localhost:1111/myserver/echoanyheader/user-agent
Will in my case return
Mozilla/5.0 (Windows NT 10.0;Win64;x64) AppleWebKit/537.36 (KHTML,like Gecko) Chrome/70.0.3538.110 Safari/537.36
Some HTTP headers may contain qualifiers or options as part of the headers value. For example the Accept-Language header that often is sent with every browser request, and that tells the server which spoken language(s) the client will accept the response in (or more accurately, prefer the response in). Example:
Accept-Language: de
In this example, the browser absolutely prefer all returned data to be localized into German.
Accept-Language: de; q=1.0, en; q=0.5
However if this value was provided by the browser, the server should send data localized into German if at all possible, else it should be localized into English.
Hence the Accept-Language is in fact a value that contains underlying fields (de and en) which in turn may have options attached to them (q).
kbmMW provides a way to access these (already parsed) values too:
function TMyService.EchoAnyHeader(const AHeaderName:string):string; var i:integer; hlp:TkbmMWHTTPTransportStreamHelper; headervalue:TkbmMWHTTPMimeHeaderValue; field:TkbmMWHTTPMimeHeaderValueField; option:TkbmMWHTTPMimeHeaderValueFieldOption; begin hlp:=TkbmMWHTTPTransportStreamHelper(RequestTransportStream.Helper); headervalue:=TkbmMWHTTPMimeHeaderValue(hlp.Header.ValueByName['Accept-Language']); if headervalue<>nil then begin // Access the de, en etc. fields of the header value. for i:=0 to headervalue.Fields.Count-1 do begin field:=TkbmMWHTTPMimeHeaderFieldValue(headervalue.Fields.Values[i]); // field.Name to access the fields name. Eg. de or en in this case. // field.AsString to get or set the textual representation of the field. for j:=0 to field.Options.Count-1 do begin option:=TkbmMWHTTPMimeHeaderValueFieldOption(field.Options.Values[i]); // option.Name is in this case 'q' // option.AsString to get or set the textual representation of the field. // In this case it could be 1.0 or 0.5 etc. end; end; end; end;
As you can see, each level has the AsString and AsEncodedString propertty accessible, which makes it possible to set or get the textual representation of that particular value in the mime header value hierarchy.
If you like kbmMW, share the word. Reshare the blog posts and let others know about the product!
Essentially help us to help you 🙂