Async NancyFX with HttpClient - GetAsync for fbcdn returns 403 forbidden?
.net, async-await, c#, dotnet-httpclient, nancy
Solution
The problem is in URL Retrieval from Nancy request, not getting Data with `HttpClient`.
I Suppose you are sending request to nancy like:
http://localhost/?url=...
hence for Facebook, it would be:
http://localhost/?url=https://scontent.xx.fbcdn.net/v/t1.0-1/c15.0.50.50/p50x50/10354686_10150004552801856_220367501106153455_n.jpg?oh=6c801f82cd5a32fd6e5a4258ce00a314&oe=589AAD2F
but for this url `string url = Request.Query["url"].Value.ToString();` is incomplete and missing the last part (&oe=589AAD2F), hence the server responses with Forbidden.
Here is the simple change to demonstrate the problem:
private async Task<Response> GetResult(dynamic parameters, CancellationToken ct)
{
var client = new HttpClient();
var req = Request.Url.ToString();
var queryStart = req.IndexOf("url=");
if (queryStart == -1)
return Nancy.HttpStatusCode.BadRequest;
var url = req.Substring(queryStart + 4);
if (string.IsNullOrEmpty(url))
return Nancy.HttpStatusCode.BadRequest;
client.DefaultRequestHeaders.Add("Access-Control-Allow-Origin", "*");
client.DefaultRequestHeaders.Add("User-Agent",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36");
client.DefaultRequestHeaders.Add("Upgrade-Insecure-Requests", "1");
client.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, sdch, br");
client.DefaultRequestHeaders.Add("Accept-Language", "en-US,en;q=0.8,ru;q=0.6");
var response = await client.GetAsync(url, ct);
ct.ThrowIfCancellationRequested();
switch (response.StatusCode)
{
case System.Net.HttpStatusCode.OK:
var stream = await response.Content.ReadAsStreamAsync();
return Response.FromStream(stream, response.Content.Headers.ContentType != null
? response.Content.Headers.ContentType.ToString()
: "application/octet-stream");
default:
return Response.AsText("\nError " + response.StatusCode);
}
}
Solution
We can actually Encode the URL prior sending it, and Nancy will Automatically decode the URL for us, hence no need to change anything server side.
Here is a link example generated using `HttpUtility.UrlEncode` applied on
https://scontent.xx.fbcdn.net/v/t1.0-1/c15.0.50.50/p50x50/10354686_10150004552801856_220367501106153455_n.jpg?oh=6c801f82cd5a32fd6e5a4258ce00a314&oe=589AAD2F
Result:
https%3a%2f%2fscontent.xx.fbcdn.net%2fv%2ft1.0-1%2fc15.0.50.50%2fp50x50%2f10354686_10150004552801856_220367501106153455_n.jpg%3foh%3d6c801f82cd5a32fd6e5a4258ce00a314%26oe%3d589AAD2F
and the actual request for this specific link would be:
http://localhost:9876/?url=https%3a%2f%2fscontent.xx.fbcdn.net%2fv%2ft1.0-1%2fc15.0.50.50%2fp50x50%2f10354686_10150004552801856_220367501106153455_n.jpg%3foh%3d6c801f82cd5a32fd6e5a4258ce00a314%26oe%3d589AAD2F
Alternate Solution
I personally prefer POST over GET in this situation, so here it is:
public class ReverseProxyController : NancyModule
{
class ProxyRequest
{
public string Url { get; set; }
}
public ReverseProxyController()
{
Post["/", true] = async (parameters, ct) =>
{
var result = await GetResult(parameters, ct);
return result;
};
}
private async Task<Response> GetResult(dynamic parameters, CancellationToken ct)
{
var pReq = this.Bind<ProxyRequest>();
var url = pReq.Url;
if (string.IsNullOrEmpty(url))
return null;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Access-Control-Allow-Origin", "*");
client.DefaultRequestHeaders.Add("User-Agent",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36");
client.DefaultRequestHeaders.Add("Upgrade-Insecure-Requests", "1");
client.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, sdch, br");
client.DefaultRequestHeaders.Add("Accept-Language", "en-US,en;q=0.8,ru;q=0.6");
var response = await client.GetAsync(url, ct);
ct.ThrowIfCancellationRequested();
switch (response.StatusCode)
{
case System.Net.HttpStatusCode.OK:
var stream = await response.Content.ReadAsStreamAsync();
return Response.FromStream(stream, response.Content.Headers.ContentType != null
? response.Content.Headers.ContentType.ToString()
: "application/octet-stream");
default:
return Response.AsText("\nError " + response.StatusCode);
}
}
}
Problem
NOTE: I've changed this question quite a bit in an attempt to make it point more to the problem. The comments below no longer reflect this question. I'm trying to get this image from fbcdn: https://scontent.xx.fbcdn.net/v/t1.0-1/c15.0.50.50/p50x50/10354686_10150004552801856_220367501106153455_n.jpg?oh=6c801f82cd5a32fd6e5a4258ce00a314&oe=589AAD2F Browser gets it just fine. Here's my code: ``` public class ReverseProxyController : NancyModule { public ReverseProxyController() { Get["/", true] = async (parameters, ct) => { var result = await GetResult(parameters, ct); return result; }; } private async Task<Response> GetResult(dynamic parameters, CancellationToken ct) { var client = new HttpClient(); string url = Request.Query["url"].Value.ToString(); if (url == null) return null; client.DefaultRequestHeaders.Add("Access-Control-Allow-Origin", "*"); client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36"); client.DefaultRequestHeaders.Add("Upgrade-Insecure-Requests", "1"); client.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"); client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, sdch, br"); client.DefaultRequestHeaders.Add("Accept-Language", "en-US,en;q=0.8,ru;q=0.6"); var response = await client.GetAsync(url, ct); ct.ThrowIfCancellationRequested(); switch (response.StatusCode) { case HttpStatusCode.OK: var stream = await response.Content.ReadAsStreamAsync(); return Response.FromStream(stream, response.Content.Headers.ContentType != null ? response.Content.Headers.ContentType.ToString() : "application/octet-stream"); default: return Response.AsText("\nError " + response.StatusCode); } } } ``` I get a 403 Forbidden response every time. I thought adding the headers would make it work but no go. This code works for other images on other hosts like this one: https://s-media-cache-ak0.pinimg.com/564x/ff/f0/c9/fff0c988a4516659d4009f60e0694cb6.jpg