ThinkGeo.com    |     Documentation    |     Premium Support

WMS -- GetMap Request

MapSuite Team,

My WMS provider keeps historical imagery, which I have to support to allow a user to request imagery that is other than the latest imagery.

To do this the WMS Server requires that the date be placed in the GetMap Request as shown in the statements below. Is there a means whereby the TIME parameter can be added to the request?

Thanks,
Dennis

MAP
SIZE 800 650
EXTENT 593133.398829 170094.734526 593893.148973 170712.010326
STATUS ON
UNITS FEET
IMAGETYPE JPEG
PROJECTION
“init=epsg:200068”
END
LAYER
NAME “20150427b”
TYPE RASTER
STATUS DEFAULT
CONNECTION "https://wmsus.nearmap.com/wms?TIME=2015-04-27&apikey=
CONNECTIONTYPE WMS
#PROCESSING “OVERSAMPLE_RATIO=1.0”
METADATA
“ows_title” “NearMap”
“wms_name” “NearMap”
#“wms_name” “ExactDate”
“wms_server_version” “1.1.1”
“wms_format” “image/jpeg”
“wms_srs” “EPSG:3857”
“gml_include_items” “all”
END
PROJECTION
“init=epsg:3857”
END
CLASS
NAME “Nearmap Imagery”
END
END
END

Hi Dennis,

It looks I am not sure about what’s the data after your post.

If you want to sent special parameter, you can just add the parameter in the layer or directly modify the sending request in the sending request event.

And then you should want to parse them in your server side.

So what’s the mainly problem you met?

Regards,

Ethan

hi Ethan,

What I need to do is add TIME=2015-04-27 to the Connection in order to request an image from a certain date.

I will try your suggestion of modifying the string in the SendingWebRequest event.

Thanks for your suggestion.

Dennis

Hi Dennis,

Any question please let us know.

Regards,

Ethan

hi Ethan,

Below is the string from the WebRequest. It is inside this string that my WMS Provider is telling me that I have to put the TIME=CCYYMMDD parameter.

So you’re saying it is possible to modify this string inside the SendingWebRequest event?

Thanks,
Dennis

https://wmsus.nearmap.com/wms?apikey=RemovedForSecurityReasons&REQUEST=GetMap&BBOX=-8771988.35840726,4377593.60272568,-8145817.7888295,5003764.17230344&WIDTH=512&HEIGHT=512&LAYERS=NearMap\United_States_of_America\OHIO&STYLES=&FORMAT=image/jpeg&SRS=EPSG:3857&VERSION=1.1.1&SERVICE=WMS&EXCEPTIONS=

Hi Dennis,

You can do that like this:

 public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void map_Loaded(object sender, RoutedEventArgs e)
    {
        map.MapUnit = GeographyUnit.DecimalDegree;

        WmsRasterLayer wms = new WmsRasterLayer(new Uri(@"http://howdoiwms.thinkgeo.com/WmsServer.aspx"));
        wms.SendingWebRequest += Wms_SendingWebRequest;

        LayerOverlay layerOverlay = new LayerOverlay();
        layerOverlay.Layers.Add(wms);
        map.Overlays.Add(layerOverlay);

        map.CurrentExtent = new ThinkGeo.MapSuite.Shapes.RectangleShape(-180, 90, 180, -90);
        map.Refresh();
    }

    string YourTime = "CCYYMMDD";

    private void Wms_SendingWebRequest(object sender, SendingWebRequestEventArgs e)
    {
        e.WebRequest = System.Net.HttpWebRequest.Create(e.WebRequest.RequestUri.ToString() + String.Format("&Time={0}", YourTime));
    }
}

Regards,

Ethan

hi Ethan,

Just wanted to let you know that I have recently implemented your suggestion and it works great when my client application is connected directly to the remote WMS Service Provider located in the cloud.

However, when the client application is connected to the local WMS Server it does not work.

If you recall my configuration is WmsClient–>>WmsServerLocal–>>WmsServerRemote.

So the client sends the request to the local WmsServer and then the local WmsServer forwards the request on to the remote WmsServer.

The new field that is being added to the message is TIME, which actually contains a date. The TIME field is being passed from client to ProcessRequestCore of the local WmsServer Handler. However, the SendingWebRequest event in the local WmsServer no longer has the TIME. See traces below.

By looking at these traces it would appear that by the time the request gets to the local WmsServer SendingWebRequest event the sequence of the fields is totally different than seen in the first two traces. This tells me that the request is being rebuilt possibly by base.ProcessRequestCore resulting in the TIME field not being included.

What can be done to ensure that the TIME field is carried forward?

Thanks,
Dennis
OriStar Mapping, Inc.

From Client SendingWebRequest–>>
REQUEST=GetMap&BBOX=-9759552.48234785,5163669.84416693,-9759246.73499948,5163975.5915153&WIDTH=512&HEIGHT=512&LAYERS=NearMapChicagoOemc&STYLES=&FORMAT=image/jpeg&SRS=EPSG:3857&VERSION=1.1.1&SERVICE=WMS&EXCEPTIONS=&TIME=2017-06-15

From MapSuiteWmsServer Handler ProcessRequestCore–>
{REQUEST=GetMap&BBOX=-9759552.48234785%2c5163669.84416693%2c-9759246.73499948%2c5163975.5915153&WIDTH=512&HEIGHT=512&LAYERS=NearMapChicagoOemc&STYLES=&FORMAT=image%2fjpeg&SRS=EPSG%3a3857&VERSION=1.1.1&SERVICE=WMS&EXCEPTIONS=&TIME=2017-06-15}

From MapSuiteWmsServer SendingWebRequest–>>
&BBOX=-9759552.48234785,5163669.84416693,-9759246.73499948,5163975.5915153&EXCEPTIONS=&FORMAT=image/jpeg&HEIGHT=512&LAYERS=NearMap\United_States_of_America\ILL,NearMap\United_States_of_America\IND&REQUEST=GetMap&SERVICE=WMS&SRS=EPSG:3857&STYLES=&VERSION=1.1.1&WIDTH=512"

Hi Dennis,

Just like you mentioned, the WmsServerLocal is also be a server, so it will parse the parameters. If you want to use it forward the request, I think you should want to write the custom code to parse the parameters and then build the new url yourself.

If you found the code base.ProcessRequestCore is the key, you can override it and add the time parameter again.

But I think you should can add that in your custom code where you forward the request.

Regards,

Ethan

Hi Ethan,

Thanks for getting back to me.

My Local WmsServer already has an override for ProcessRequestCore as shown below. The override provides logging of the requests. By setting a breakpoint in this event I know the TIME field is present as shown in my previous post. After logging is complete base.ProcessRequestCore is invoked so that processing may continue, which results in the SendingWebRequest event being fired.

In SendingWebRequest the TIME field has been removed and is nowhere to be found in the arguments passed to the event. Therefore, it cannot be added to the WebRequest because the value is no longer known and there is no way to know what that value was.

Remember that the ProcessRequestCore and SendingWebRequest are two separate events with no way to pass data from one to the other besides the actual original formatted request string. Remember too that the Local WmsServer is merely a pass through and has no knowledge of the original client request except through the request string.

The solution, I believe, is to do a complete override of ProcessRequestCore, add the TIME field, and remove the call to base.ProcessRequestCore. The request string that ultimately ends up at SendingWebRequest should then have the TIME field.

Would you provide the code required to completely override ProcessRequestCore? I assume this code will have the build of the standard request string and any other necessary code to ensure that SendingWebRequest is fired. I’m not asking for any code other than what is now within ProcessRequestCore.

Thanks,
Dennis
OriStar Mapping, Inc.

    protected override void ProcessRequestCore(HttpContext TheContext)
    {
        Boolean bOutCome;
        string TheLogMessage;
        string TheLogMessageSub;

        string TheIpAddress;
        string TheRequestType;
        string TheRequestTypeUpCase;

        NameValueCollection TheRequestQuery;

        OriStarToolsWms.clsWmsClient TheWmsClient;
        OriStarLogging.UtlLog4Net.LoggingLevel TheLoggingLevel;

        // initializations
        TheIpAddress     = string.Empty;
        TheLogMessageSub = string.Empty;


        // get Client IpAddress
        TheIpAddress = TheContext.Request.UserHostAddress;

        // get Client RequestType
        TheRequestQuery      = TheContext.Request.QueryString;
        TheRequestType       = TheRequestQuery["REQUEST"];
        TheRequestTypeUpCase = TheRequestQuery["REQUEST"].ToUpperInvariant();

        // instantiate Client Object and add/update collection
        TheWmsClient            = new OriStarToolsWms.clsWmsClient();
        TheWmsClient.IpAddress  = TheIpAddress;
        TheWmsClient.MethodName = "ProcessRequestCore";
        bOutCome = OriStarWmsServer.TheAssetsStatic.aTheWmsAssetsShared.WmsClientCollection.Add(ref TheWmsClient);

        if (TheRequestTypeUpCase.Equals("GETCAPABILITIES", StringComparison.OrdinalIgnoreCase))
        {
            TheLoggingLevel = OriStarLogging.UtlLog4Net.LoggingLevel.Fine;
        }
        else
        {
            TheLoggingLevel = OriStarLogging.UtlLog4Net.LoggingLevel.Finer;
        }

        // log condition (even though the Logger checks if Level being Logged, don't even invoke method unless Level is Enabled)
        if (OriStarWmsServer.TheAssetsStatic.aTheUtlLog4Net.LoggingLevelsState[(int)TheLoggingLevel] == true)
        {
            TheLogMessage = string.Format("IpAddress={0}, HostName={1}, HttpMethod={2}, RequestType={3}", TheWmsClient.IpAddress, TheWmsClient.HostName, TheContext.Request.HttpMethod, TheRequestType);
            OriStarWmsServer.TheAssetsStatic.aTheUtlLog4Net.UtlLog4NetLogInfo(OriStarWmsServer.TheAssetsStatic.gsAppAssemblyName, TheLoggerClassNameValue, "ProcessRequestCore", ref TheLogMessage, TheLoggingLevel, (OriStarLogging.UtlLog4Net.LoggingDestination.Historical));
        }

        base.ProcessRequestCore(TheContext);

        return;
    }

Hi Dennis,

Thanks for your detail description, I think I understand your scenario now.

But the problem is I hadn’t found how you redirect the request in your code, it looks you hadn’t changed anything here, if you can let me know how you redirect the request, maybe I can help you find a solution about it.

Why I hadn’t directly paste the code here is because:

  1. The ProcessRequestCore contains many external functions, just put the code of it here cannot solve the problem.
  2. You should want to ask our sales about this requirement, support team don’t have right to decide it.

Wish that’s helpful.

Regards,

Don

hi Ethan,

If you look at my form post below and look at the June 2017 entries you’ll see how the Local WmsServer redirects a client request to the Remote WmsServer. It’s really quite simple from my perspective as it is all handled by the MapSuite WmsServer internals. It’s really just a matter of how GetMapConfigurationCore is coded.

I would agree that I really don’t want all that code in regard to ProcessRequestCore.

I have tried numerous ideas on how to get a TIME field passed to the Local WmsServer’s SendingWebRequest event and have not been able to come up with anything. I even considered using some type of state tied in with how I am doing logging as there is a collection of objects corresponding to each of the client workstations. But in the SendingWebRequest event I don’t see how to identify the original client in terms of IP Address or Http SessionId. Do you know if IP Address or SessionId is available within SendingWebRequest?

I would like to see ThinkGeo modify its’ code in order to allow TIME to pass through. In fact it does this on the client and so allows TIME to pass through to the local WmsServer. The reformatting of the request must be being done somewhere within ProcessRequestCore as that is the place where the fields of the original request are reformatted in a different order than what has come from the client request. In fact I noted this in my form post that I mentioned above. It appears ProcessRequestCore is dropping any fields it does not recognize. Instead of dropping those fields it should just allow them to pass through.

What are your thoughts?

Thanks,
Dennis
OriStar Mapping, Inc.

Hi Dennis,

Please let me clarify your question, you prefer to send one TIME parameter from client to your local server, this steps you already finished, and then you want to send out this TIME parameter from your local WMS server to nearmap server. You are stuck here. If I am wrong, please correct me.

There are two options for you:

  1. You can consider to using WMS 1.3.0, we already support the TIMS parameter follow WMS 1.3.0 protocol. You can easy to set the version with [“VERSION”] parameter, but I am not sure if it can approach your requirements;

  2. I supposed that you already created one plugin which used to send request to get the image from nearmap. In this plugin, you can override the method to “GetMapCore”, in this method, you can get the HttpContext which contains TIME parameter, and then you can send out request with the TIME parameter.

And here is the ProcessRequestCore method details in WmsHandler. You can refer that.

If you need any more information, please let us know that.

Thanks
Mark

hi Mark,

You are correct in what I want to do and where it stands.

In a previous reply I pointed out that the TIME field is contained in the request from the client to the Local WmsServer and it appears in the request in the override of ProcessRequestCore. However, when the request arrives at SendingWebRequest the TIME is no longer present.

In the Local WmsServer I set breakpoints at GetMapCore, ProcessRequestCore, and SendingWebRequest. When a request from the client was sent the breakpoints were hit in this sequence - GetMapCore, ProcessRequestCore, and SendingWebRequest. It should be noted that TIME was present in the request only at ProcessRequestCore.

In the screen capture you provided of the virtual ProcessRequestCore it’s my guess that either the Validator.CheckGetMapRequestString or Helper.ValidateIntegrity methods are where TIME is removed from the request.

Having said that…I am not sure what your intention was for providing the screen capture of the ProcessRequestCore and your reference to GetMapCore. I attempted to add the code from the screen capture into my application, but Validator and Helper are not known to my application and I do not know what namespace they are contained within nor whether I should have access to those static classes.

Would you clarify if it was your intention to have me add the code from the screen capture to my application. And if so are you saying that for the GETMAP Request I would add the TIME field back after being removed from either CheckGetMapRequestString or ValidateIntegrity?

When I specified protected virtual void ProcessRequestCore the warning as shown below is given. Do you know what this means and how to avoid it?:

I attempted to change to WmsVersion 1.3.0, but don’t believe the Remote WmsServer supports that version. When I changed to 1.3.0 I could not load any layers.

Thanks,
Dennis
OriStar Mapping, Inc.

‘OriStarWmsHandlerNonSpecific.ProcessRequestCore(HttpContext)’ hides inherited member ‘WmsHandler.ProcessRequestCore(HttpContext)’. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword. OriStarWmsServer C:\OriStarMappingInc\OriStarWmsServer\OriStarWmsServer\OriStarWmsHandlerNonSpecific.cs

Hi Dennis,

Just like I mentioned, the ProcessRequestCore contains many external code for example the Validator and Helper classes you found.

At the same time you misunderstand the Validator.CheckGetMapRequestString and Helper.ValidateIntegrity functions, they won’t modify the parameters, only adjust follow some rules, if the request don’t allow the rules, it will return false and the code won’t go into the target lines.

You also put the order incorrect, the order is:ProcessRequestCore->GetMap->GetMapCore->SendingWebRequest

That’s why we suggest you modify the parameter in the GetMapCore, you can also found the GetMap function in the screen shot code.

Wish that’s helpful.

Regards,

Ethan

hi Ethan,

Since my Local WmsServer is a pass-through from the client to the Remote WmsServer there are two GetMapCore methods.

There is a GetMapCore in the PlugIn as well as a GetMapCore in the Handler. In my previous posts I was referring to GetMapCore in the Handler having forgot all about GetMapCore in the PlugIn.

This override is in the PlugIn inherited from WmsLayerPlugin–>>
protected override Bitmap GetMapCore(GetMapRequest getMapRequest, MapConfiguration mapConfiguration, System.Web.HttpContext context)
{
return base.GetMapCore(getMapRequest, mapConfiguration, context);
}

This override is in the Handler inherited from WmsHandler–>>
protected override Bitmap GetMapCore(GetMapRequest getMapRequest, HttpContext context)
{
return base.GetMapCore(getMapRequest, context);
}

The breakpoint sequence is–>>
WmsHandler.GetMapCore (I’m not able to find TIME in Request)
WmsHandler.ProcessRequestCore (has TIME in Request)
WmsPlugIn.GetMapCore (has TIME in Request)
SendingWebRequest (no TIME in Request)

Now that WmsPlugIn.GetMapCore is in our descriptions I think it’s safe to say that WmsPlugIn.GetMap is where the TIME is being removed from the request. Do you agree?

Your last post states That’s why we suggest you modify the parameter in the GetMapCore. And now I know that is WmsPlugIn.GetMapCore. What is it that should be modified in WmsPlugIn.GetMapCore? Would you offer some sample code?

Thanks,
Dennis
OriStar Mapping, Inc.

This is the Request (with TIME parameter) from WmsPlugIn.GetMapCore–>>
{REQUEST=GetMap&BBOX=-9758940.98765111%2c5165198.58090877%2c-9758329.49295437%2c5165810.07560551&WIDTH=512&HEIGHT=512&LAYERS=NearMapChicagoOemc&STYLES=&FORMAT=image%2fjpeg&SRS=EPSG%3a3857&VERSION=1.1.1&SERVICE=WMS&EXCEPTIONS=&TIME=2017-06-15&ALL_HTTP=HTTP_HOST%3awww.oristarwms.com%3a5048%0d%0aHTTP_ASPFILTERSESSIONID%3aS(1zofw51nqu0wanbggytshmot)%0d%0a&ALL_RAW=Host%3a+www.oristarwms.com%3a5048%0d%0aAspFilterSessionId%3a+S(1zofw51nqu0wanbggytshmot)%0d%0a&APPL_MD_PATH=%2fLM%2fW3SVC%2f4%2fROOT&APPL_PHYSICAL_PATH=C%3a%5cOriStarMappingInc%5cOriStarWmsServer%5cOriStarWmsServer%5c&AUTH_TYPE=&AUTH_USER=&AUTH_PASSWORD=&LOGON_USER=&REMOTE_USER=&CERT_COOKIE=&CERT_FLAGS=&CERT_ISSUER=&CERT_KEYSIZE=&CERT_SECRETKEYSIZE=&CERT_SERIALNUMBER=&CERT_SERVER_ISSUER=&CERT_SERVER_SUBJECT=&CERT_SUBJECT=&CONTENT_LENGTH=0&CONTENT_TYPE=&GATEWAY_INTERFACE=CGI%2f1.1&HTTPS=off&HTTPS_KEYSIZE=&HTTPS_SECRETKEYSIZE=&HTTPS_SERVER_ISSUER=&HTTPS_SERVER_SUBJECT=&INSTANCE_ID=4&INSTANCE_META_PATH=%2fLM%2fW3SVC%2f4&LOCAL_ADDR=127.0.0.1&PATH_INFO=%2fOriStarWmsHandlerNonSpecific.axd&PATH_TRANSLATED=C%3a%5cOriStarMappingInc%5cOriStarWmsServer%5cOriStarWmsServer%5cOriStarWmsHandlerNonSpecific.axd&QUERY_STRING=REQUEST%3dGetMap%26BBOX%3d-9758940.98765111%2c5165198.58090877%2c-9758329.49295437%2c5165810.07560551%26WIDTH%3d512%26HEIGHT%3d512%26LAYERS%3dNearMapChicagoOemc%26STYLES%3d%26FORMAT%3dimage%2fjpeg%26SRS%3dEPSG%3a3857%26VERSION%3d1.1.1%26SERVICE%3dWMS%26EXCEPTIONS%3d%26TIME%3d2017-06-15&REMOTE_ADDR=127.0.0.1&REMOTE_HOST=127.0.0.1&REMOTE_PORT=59175&REQUEST_METHOD=GET&SCRIPT_NAME=%2fOriStarWmsHandlerNonSpecific.axd&SERVER_NAME=www.oristarwms.com&SERVER_PORT=5048&SERVER_PORT_SECURE=0&SERVER_PROTOCOL=HTTP%2f1.1&SERVER_SOFTWARE=Microsoft-IIS%2f10.0&URL=%2fOriStarWmsHandlerNonSpecific.axd&HTTP_HOST=www.oristarwms.com%3a5048&HTTP_ASPFILTERSESSIONID=S(1zofw51nqu0wanbggytshmot)}

Hi Dennis,

You misunderstand my reply, I hadn’t mentioned about the WmsPlugIn, I just talk about the WmsHandler.GetMapCore.

I did a quickly test based on our sample, I run the sample and get a tile image like this:
http://localhost:59387/WmsHandler.axd?LAYERS=Log%20Server%20Requests&STYLES=DEFAULT&FORMAT=image%2Fpng&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&EXCEPTIONS=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A4326&BBOX=-90,0,-45,45&WIDTH=256&HEIGHT=256

Then I add the Time parameter after it like this:
http://localhost:59387/WmsHandler.axd?LAYERS=Log%20Server%20Requests&STYLES=DEFAULT&FORMAT=image%2Fpng&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&EXCEPTIONS=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A4326&BBOX=-90,0,-45,45&WIDTH=256&HEIGHT=256&TIME=2018.4.12

And I add break point in the GetMapCore function of WmsHandler and you can see I succeed get the TIME parameter there:

And I double check the code, we won’t remove anything from request parameters.

So I think you should want to check your code and see why you cannot get the time parameter in your WmsHandler.GetMapCore.

Regards,

Ethan

hi Ethan,

Thank you for your example of how to ‘watch’ for the TIME parameter. I did it just like you did and I do see the TIME.

However, my original quandary still exists.

The TIME field is in the request for both GetMapCore and ProcessRequestCore, but it is not in SendingWebRequest.

How do I get TIME to appear in SendingWebRequest?

Appreciate your help with this.

Dennis
OriStarMapping, Inc.

Hi Dennis,

Our developers discussed about your scenario but we still confused about how you implement that detail, we guess you have some special reason to use SendingWebRequest in your scenario but we don’t know why it’s necessary.

Could you please upload a simple sample contains how you implement your client server, and a request link so we can debug it. Our developer can help you solve that quickly instead of guess about how your did that.

In fact we also want to make sure about the TIME parameter, does that have to be different for each request tile uri or it’s only be a rough value for example point to a whole day?

Because we cannot find the TIME in the SendingWebRequest event, so we think if the TIME is point to one day or an hour or even a minute, you can put a static variable to save it and pick the TIME from SendingWebRequest.

But if that’s a special value for each tile request, you cannot do that, here maybe you can try to create a static dictionary, and save a key value pair from request url, the key you can use BBOX or some other special parameter, the value you can saved as TIME, and you can pick it back in the SendingWebRequest event.

Wish that’s helpful.

Regards,

Ethan

hi Ethan,

In the first paragraph of your previous reply would you clarify what you mean by “Confused about how you implement that detail”? What detail do you refer?

Very difficult to make a sample project actually. I’d rather just put the whole project on my ThinkGeo File space. However, the Remote WmsServer requires an ApiKey so you would not get any information back.

The TIME parameter is a date that the user selects via the UI and once selected it is that date that is used for all requests from that point forward, until they select another date.

What I had done three weeks ago was to extract the TIME parameter from the ProcessRequestCore (now changed to GetMapCore) of the Local WmsServer and then set a public static property with that value. Then in the SendingWebRequest get that TIME value and add it to the WebRequest.

I feel that is not a very clean way to implement, but did it as a temporary measure so that I could continue with development.

My concern about doing it this way is being guaranteed that processing for a given GET is contiguous from ProcessRequestCore to GetMapCore to SendingWebRequest and then back. The Local WmsServer serves many client workstations so the concern is that when requests come in very fast the TIME value will be corrupted.

Do these three events operate in a contiguous fashion as I described above? In other words, it is single-threaded?

Dennis
OriStar Mapping, Inc.

Hi Dennis,

This is long conversation. :slight_smile:

I checked above conversation and checked our source code. Please refer below information:

  1. SendingWebRequest event used to handle the current tile related information, you can append or update the sending request url, but this event won’t/can’t get the information from other tiles.

  2. The sending request URL is constructed with private class, and there is no TIME parameter in sending url. So you CANNOT get the correct TIME parameter which you prepared in GetMapCore or ProcessRequestCore.

  3. And you real want to send the TIME parameter to the Nearmap server, so there are three options for you:

    a. Options I: You have already implemented the parameter with public static property, but with this way, all the request from client will update the property, you have mentioned that in above. there is no solution to avoid that issue. I am not sure if you can accept that situation.

    b. Options II: Did you consider to append the DateTime.Now for TIME when sending request? this time parameter is not get from client, it is only prepared from Server side. If the time parameter is different based on the user client request, please refer Options III.

    c. Option III: If your time parameter is according with the client side request, in another world, you prefer different client user may send different TIME parameter. With this situation, you can consider to send the client Id (maybe one GUID) and TIME with key value, and store that on server, like the Options I you already implemented, and then append the TIME based on current client.

Hope this make scene to you. If you need any more information, please let me know that.

Thanks
Mark