1

I'm using ServiceStack for a while now and I'm very happy with the functionality it provides. Already implemented serveral services with it and it works like a charm.

Recently however I've faced a problem with calling other service with a sophisticated URL that has to be encoded properly.

The code is the following:

The Request:

[Route("/instruments/{Names}")]
internal class Request
{
    public List<string> Names { get; set; }
}

And the method call:

var request = new Request { Names = list };
var c = new JsonServiceClient("http://host:12345/");

Response[] response;
try
{
    response = c.Get<Response[]>(request);
}
catch (WebServiceException ex)
{
    HandleWebException(ex, list);
    yield break;
}

And now the problem is that sometimes the name can contain a special characters like space or /. I'd like to have those propery encoded. For instance I'd like to be able to call the remote service with the following parameters: "D\S N" with is supposed to be encoded to "D%5CS%20N". So the called URL should look something like this:

http://host:12345/instruments/D%5CS%20N

And now the problem is that JsonServiceClient does the bad encoding here. What I call is actually:

http://host:12345/instruments/D/S%20N

With is obviously wrong. Any help how to sort this out is appeciated.

3 Answers3

1

You shouldn't register complex types like List<T> in the PathInfo, try with a single string:

[Route("/instruments/{Name}")]
public class Request
{
    public string Name { get; set; }
}

Or take it out of the /pathinfo so it will get serialized as a complex type on the QueryString:

[Route("/instruments")]
public class Request
{
    public List<string> Names { get; set; }
}
mythz
  • 141,670
  • 29
  • 246
  • 390
  • Hi Demis, thanks for your answer. Unfortunetly it doesn't change anything. Client is still calling the following URL: http://host:12345/instruments/D/S%20N. Funny thing is that when I change the Names parameter to be a query parameter instead of Path parameter, it works correctly: http://host:12345/instruments?names=D%5CS%20N. Unfortuneatly I cannot change this interface. – Hubert R. Skrzypek Nov 25 '14 at 13:22
  • You can't have a `/` in the param it would be treated like a separate component. You can change it to use a wildcard param which will soak up all the path components, i.e. `[Route("/instruments/{Name*}")]` – mythz Nov 25 '14 at 14:37
  • The problem here is that I have \ in my param, not /. But after I pass a name with \ to the client, it somehow changes it to /. I have `D\S N` and it should be encoded to `D%5CS%20N` but instead I can see that Client is calling an url with this name encoded as `D/S%20N`. Why is that? – Hubert R. Skrzypek Nov 26 '14 at 12:06
  • @HubertR.Skrzypek the issue is because [backslashes are illegal in urls](http://stackoverflow.com/a/10444621/85785) which browsers automatically convert to `/`. Likewise in HttpListener the \ is automatically converted to `/` whereas the ASP.NET webdev server will automatically throw a `400 Invalid Request`. Basically backslashes does not have reliable behavior in urls so they should be avoided. – mythz Nov 26 '14 at 14:09
  • Yep, but I'd like to use encoded backslashes in my URLs, the problem is that ServiceStack somehow instead of encoding them as `%5C` uses `/`. When I call the other service directly from the browser with URL encoded as following `host:12345/instruments?names=D%5CS%20N` it works as a charm. – Hubert R. Skrzypek Dec 01 '14 at 15:51
  • @HubertR.Skrzypek No ServiceStack isn't doing the conversion, the host converts it before it reaches ServiceStack. You can view this [Route Test](https://github.com/ServiceStack/ServiceStack/blob/8a34779d4f23ff155fddcdfd917ab7fd6d5b0173/tests/ServiceStack.WebHost.Endpoints.Tests/RouteTests.cs#L94-L105) to see what ServiceStack sends. The takeaway is never to use `\\` in urls, encoded or otherwise. But it's safe to use it on the QueryString as you've discovered. – mythz Dec 01 '14 at 15:58
1

I believe ServiceStack could be improved here.

If I use a request DTO defined like this:

[Route("/hello/{Name}/{Message}")]
public class Hello
{
    public string Name { get; set; }
    public string Message { get; set; }
}

Then a client calling like this:

var resp = cli.Get(new Hello { Message = "test", Name = "foo/bar" });

will fail. Same happens if I replace the slash with a backslash.

I have made a patch to ServiceStack that fixes this behaviour (works for backslash too), so that Name will be correctly encoded client side and decoded server side. Demis, is this something you might be interested in taking a look at?

BTW this works fine out-of-the-box with Java Jersey.....

0

I am encountering the same problem. My string is not a complex object. Just a string with a slash in it. It seems like ServiceStack is in fact receiving the URL encoded string correctly. Servicestack then appears to be decoding the URL encoded string before it passes it to routing (this is just a guess on my part) , instead of using the Route information in the request DTO to first determine which part of the URL is routing and which part is a parameter, then routing, then decoding the URL encoded parameter. I receive an error from service stack like so:

Handler for Request not found (404):

  Request.HttpMethod: GET
  Request.PathInfo: /QuoteFeed/GetQuote/ACO/X CN
  Request.QueryString: 
  Request.RawUrl: /QuoteFeed/GetQuote/ACO%2FX%20CN

Request is defined as follows:

[Route("/QuoteFeed/GetQuote/{Symbol}", Summary = "Retreive a price quote for the requested symbol.")]
public class GetQuote : IReturn<QuoteDataResponse>
{
    [DataMember, ProtoMember(1)]
    [ApiMember(Name = "Symbol",
         Description = "The symbol, in the providers given format, for which a quote should be given.",
         DataType = "string",
         IsRequired = true)]
    public string Symbol { get; set; } 
}

Seems fragile to require the client to replace slashes with some other special character which the service would swap back to a slash. Is the only way around this to force the request through POST?

Edit 1

This is my best attempt at getting this to work:

On the client side:

dim client = new JsonServiceClient (My.Settings.MarketDataServiceUri)
dim request = New GetQuote
request.Symbol =  WebUtility.UrlEncode(txtBoxSymbol.Text.ToUpperInvariant)

On the server side (in AppHost.Init):

base.RequestBinders.Add(typeof(GetQuote), httpReq =>
{
    var requestPath = string.Empty;
    if (typeof(GetQuote).GetCustomAttributes(typeof(RouteAttribute), true
                                     ).FirstOrDefault() is RouteAttribute dnAttribute)
    {
        requestPath = dnAttribute.Path;
    }

    var routePath = requestPath;
    var paramIndex = requestPath.IndexOf("{");
    if (paramIndex > 0)
    {
        routePath = requestPath.Substring(0, paramIndex);
    }

    return new GetQuote
    {
        Symbol = WebUtility.UrlDecode(httpReq.PathInfo.Replace(routePath, string.Empty))
    };
});

This is really ugly, but I can't find any other way around this. The symbol being passed in must have the slashes since that is the format required by the downstream quote provider and is what the users expect to enter. Is this the best way to do this?

Bitfiddler
  • 3,942
  • 7
  • 36
  • 51
  • I'd avoid using slashes in the /path/info at all, backslashes are illegal in URLs , explained in [existing comment thread below](https://stackoverflow.com/questions/27102001/servicestack-uri-encoding/53838191#comment42797035_27115542) whilst forward slashes represents a different path component + matching route. You can avoid any ambiguity by sending it in the queryString, e.g. `?symbol=ACO%2FX+CN` or change the Symbol to a [wildcard path](https://docs.servicestack.net/routing#wildcard-paths), e.g. `{Symbol*}` so it matches multiple path segments. – mythz Dec 18 '18 at 18:47
  • I thought asking a new question was not appropriate since it's pretty much the same problem, just my situation is a bit different in that I am not doing an complex object inside the string. I tried sending it through the query string but same problem: Handler for Request not found (404): Request.HttpMethod: GET Request.PathInfo: /QuoteFeed/GetQuote Request.QueryString: symbol=ACO%2fX+CN Request.RawUrl: /QuoteFeed/GetQuote?symbol=ACO%2FX+CN – Bitfiddler Dec 18 '18 at 18:52
  • Right now I am pre-urlEncoding the string before I assign the symbol to the request Dto and then using a RequestBinder to UrlDecode it on the server side, it's not pretty but it works. Would be nice if there was a less fiddly way to do this. – Bitfiddler Dec 18 '18 at 18:55
  • Do you want me to delete and repost? – Bitfiddler Dec 18 '18 at 19:02
  • You shouldn't have issues with either of my solutions. You will still need to encode the value in the URL (e.g. using `encodeURIComponent()` in JavaScript), if you try put the unencoded value in a browser (e.g. `?symbol=ACO/X CN`) the browser will automatically encode it for you. – mythz Dec 18 '18 at 19:02
  • Nope that's ok, leaving a partial answer/solution works. I can delete my invalidated comments. – mythz Dec 18 '18 at 19:03
  • FYI If you want to use the queryString for symbol you should remove it from the route so it doesn't get included in the /path/info, e.g. `[Route("/QuoteFeed/GetQuote"]`. – mythz Dec 18 '18 at 19:05
  • So entering http://serverName:portNumber/QuoteFeed/GetQuote?symbol=ACO/X CN into the browser returns: Handler for Request not found (404): Request.HttpMethod: GET Request.PathInfo: /QuoteFeed/GetQuote Request.QueryString: symbol=ACO%2fX+CN Request.RawUrl: /QuoteFeed/GetQuote?symbol=ACO/X%20CN – Bitfiddler Dec 18 '18 at 19:05
  • Oh, your last comment is new to me. I thought you needed the queryString parameter in the route in order for the Dto to get it's property mapped. Ok, let me try that. – Bitfiddler Dec 18 '18 at 19:07
  • 1
    Wonderful, looks like removing {symbol} from the route did the trick. I have been working off examples where the variable placeholder was always included so I just assumed it was required for serialization of dto properties. Thank you very much for your help, more RTFM is in my future. – Bitfiddler Dec 18 '18 at 21:06