简体   繁体   中英

Determine the HTTP verb of an endpoint based on the URL

For a project I've worked on in the past, we store a URL in a field in the app's database, so that when a user has to step away, power down their system, or whatever, that they can resume working on something by opening the affected record. This URL refers to a Controller method defined in our exposed Web API for the application in question, with the requisite parameters provided to open the affected record.

There's an issue, where we're noting that - very rarely - the endpoint that's being stored refers to a POST endpoint, not a GET endpoint. When this condition is triggered, we get an exception like the following:

The parameters dictionary contains a null entry for parameter 'MyParameter' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult SomeEndpoint(Int32, Int32)' in 'MyApplication'.  An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
Parameter name: parameters

We know what we want to do to solve the problem: we want to scan the URL and determine the verb. If the verb is POST, we want to redirect to a specific URL, to prevent our application from getting stuck in this error-prone state. We want to institute this redirect to occur on the server side, not only for security reasons, but because it's simply not a client code concern.

I've done a cursory Google search on this topic, and have found no articles that talk about ways to reverse-engineer the verb of a given URL.

Question: Is there any way, given some URL, to determine what HTTP verb is being referred to in C# ?

If there is not - something I would expect, most likely for security reasons - is there any advice on techniques that avoid this sort of problem? Prior to posting this question, I have done a code audit of the affected application, and have not been able to easily reproduce the condition leading to this question (storing a POST URL in our database), and have found no logic that should lead to a POST URL being saved to our database.

Any URL can be used with any HTTP verb, including GET, POST, PUT, DELETE, and others. Therefore there is no way to infer a verb given only the URL.

Even if you could figure out when the verb is a POST, the URL is not sufficient to recreate the page, because the POST requires information held in the body (not the URL) of the request.

It sounds like your application offers sort of a bookmark feature that is held server side. If the intention is to allow the user to pick up where they left off, you have a few options.

  1. Instead of storing the URL only, store the full HTTP request, including the verb and the form body. If any of the values are session-specific (eg a session cookie), then you will need to be able to substitute new values for those somehow. Be very careful with this as there could be unforeseen consequences, eg if the POST operation did something that cannot be repeated. Also, it is a very bad idea to store the POST request of anything involved in authentication, such as the login page.

  2. Instead of bringing the user back to the target of the POST, bring the user back to the page that originated the POST. One way to do this is to program all of the controllers that handle a POST to validate the form variables; if they are completely missing, redirect to the page which the user was supposed to fill in.

  3. Consider letting the user manage his or her own bookmarks, using browser features instead of server logic.

So, if you're using some kind of Action Filter, as we are:

public class SomeActionFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        CacheCurrentStep(filterContext.HttpContext.Request);
    }

    private static void CacheCurrentStep(HttpRequestBase request)
    {
        string url = request.Url.PathAndQuery;
        // Save it, however you do that
    }
}

...It is possible to sniff the request to get the acting verb!

private static void CacheCurrentStep(HttpRequestBase request)
{
    if(request.HttpMethod.ToUpper() != "GET")
        return;

    string url = request.Url.PathAndQuery;
    // Save it, however you do that
}

The HttpRequest class has an exposed HttpMethod property that returns either HEAD, GET, or POST. There's a related property on HttpRequest, RequestType, which only returns GET or POST, that could be used instead.

For any future readers who've never used an action filter, you'd just annotate the controller with the attribute...

[SomeAction]
public class MyApiController : ApiController
{
    // Endpoints...
}

...And you're set.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM