I am attempting to forward custom parameters to a RESTful API server and return the proxied response to the client-facing server. I don't want the client to have access to or be able to read the API HTTP request/response interactions, so I decided to perform this action using a reverse proxy. I have no problem forwarding the request and returning a response. The problem lies in the authentication. The client-facing server always wants to redirect to the login page because it doesn't believe the client is authenticated. I have tried using HTTPS and HTTP with similar results.
I have been researching this problem for quite some time and found quite a variety of answers, none of which seem to quite encompass my specific use case. I am following this example, which is the closest to what I specifically need. However, the credentials portion the author commented out (//request.Credentials = CredentialCache.DefaultCredentials;) doesn't seem to cover the authentication portion I am attempting to implement. Please help me understand this problem and solution.
Here is the code I am using from the controller:
public ActionResult ProxyEndpoint(string custom_string, string another_custom_string)
{
//Bunch of code here to grab the remoteUrl from AppConfig and do stuff to the parameters and store them in queryString, unnecessary to show here.
//Here's the important bits:
remoteUrl = remoteUrl + "?" + queryString; // create my remoteUrl
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(remoteUrl);
request.Credentials = CredentialCache.DefaultCredentials;
// Also tried this to no avail:
request.Credentials = CredentialCache.DefaultNetworkCredentials;
return ProxyActionResult(request.GetResponse());
}
Here is the ProxyActionResult class:
public class ProxyActionResult : ActionResult
{
WebResponse _response;
public ProxyActionResult(WebResponse response)
{
_response = response;
}
public override void ExecuteResult(ControllerContext controllerContext)
{
HttpContextBase httpContext = controllerContext.HttpContext;
WebResponse response = _response;
// Read the byte stream from the response:
Stream responseStream = response.GetResponseStream();
// Pulled this next piece from http://www.codeproject.com/Articles/7135/Simple-HTTP-Reverse-Proxy-with-ASP-NET-and-IIS
// Seemed to fit our use case.
if ((response.ContentType.ToLower().IndexOf("html") >= 0) || (response.ContentType.ToLower().IndexOf("javascript") >= 0))// || (response.ContentType.ToLower().IndexOf("image") >= 0))
{
//If the response is HTML Content, parse it like HTML:
StreamReader readStream = new StreamReader(responseStream, Encoding.Default);
String content;
content = ParseHtmlResponse(readStream.ReadToEnd(), httpContext.Request.ApplicationPath);
//Write the updated HTML to the client(and then close the response):
httpContext.Response.Write(content);
httpContext.Response.ContentType = response.ContentType;
response.Close();
httpContext.Response.End();
}
else
{
// If the response is not HTML Content, write the stream directly to the client:
var buffer = new byte[1024];
int bytes = 0;
while ((bytes = responseStream.Read(buffer, 0, 1024)) > 0)
{
httpContext.Response.OutputStream.Write(buffer, 0, bytes);
}
// from http://www.dotnetperls.com/response-binarywrite
httpContext.Response.ContentType = response.ContentType; // Set the appropriate content type of the response stream.
// and close the stream:
response.Close();
httpContext.Response.End();
}
//throw new NotImplementedException();
}
// Debating whether we need this:
public string ParseHtmlResponse(string html, string appPath)
{
html = html.Replace("\"/", "\"" + appPath + "/");
html = html.Replace("'/", "'" + appPath + "/");
html = html.Replace("=/", "=" + appPath + "/");
return html;
}
It turns out that nothing is wrong with the reverse proxy code. The remote server was an ArcGIS OpenLayers API and it had a setting that said crossOrigin: anonymous. I commented out this setting and it worked perfectly.
Check out the documentation if you have this particular ArcGIS OpenLayers problem:
http://openlayers.org/en/v3.14.2/apidoc/ol.source.ImageWMS.html
Related
Currently trying to do a Get request as part of a c# program. The request works fine on Postman as it uses a header for authorization. However I cannot get the code working for the program to use this header correctly in its Get request. I've had a good look around and tried various bits of code I've found but haven't managed to resolve it so any help would be appreciated!
public string Connect()
{
using (WebClient wc = new WebClient())
{
string URI = "myURL.com";
wc.Headers.Add("Content-Type", "text");
wc.Headers[HttpRequestHeader.Authorization] = "Bearer OEMwNjI2ODQtMTc3OC00RkIxLTgyN0YtNzEzRkE5NzY3RTc3";//this is the entry code/key
string HtmlResult = wc.DownloadString(URI);
return HtmlResult;
}
}
Above is one method inside the class.
Below is another attempt which is an extension method that gets passed the URL:
public static string GetXml(this string destinationUrl)
{
HttpWebRequest request =
(HttpWebRequest)WebRequest.Create(destinationUrl);
request.Method = "GET";
request.Headers[HttpRequestHeader.Authorization] = "Bearer
OEMwNjI2ODQtMTc3OC00RkIxLTgyN0YtNzEzRkE5NzY3RTc3";
HttpWebResponse response;
response = (HttpWebResponse)request.GetResponse();
if (response.StatusCode == HttpStatusCode.OK)
{
Stream responseStream = response.GetResponseStream();
string responseStr = new
StreamReader(responseStream).ReadToEnd();
return responseStr;
}
else
{
Console.Write(String.Format("{0}({1})",
response.StatusDescription, response.StatusCode));
}
return null;
}
Might I recommend the very handy RestSharp package (find it on Nuget).
It turns your current code into something like
public string Connect()
{
var client = new RestClient();
var request = new RestRequest("myURL.com", Method.GET);
request.AddParameter("Authorization", "Bearer OEMwNjI2ODQtMTc3OC00RkIxLTgyN0YtNzEzRkE5NzY3RTc3");
var response = client.Execute(request);
return response.Content;
}
It's much more succinct and easier to use (in my opinion) and thus lessens the likelihood of passing in or using incorrect methods.
If you're still having issues getting data back/connecting. Then using PostMan click Code in the upper right of PostMan and select the C# (RestSharp) option. Whatever is generated there matches exactly what PostMan is sending. Copy that over and you should get data back that matches your PostMan request.
So, here's a scenario that I made up, in which I am making my own web browser and I want to make sure I'm sending correct POST text to web server.
For that to be achieved, I need to get the POST text that WebRequest creates for me before I invoke GetResponseStream().
I tried to read through the stream from WebRequest.GetRequestStream(), but I assume that isn't a way to do that.
I do NOT want plain HTML text responsed from web server.
The POST text that I need to gain must look like something like this as follows :
POST http://www.some_web_server.com/default.aspx HTTP/1.1
Cache-Control : max-age=0 Connection : keep-alive .....
Thanks in advance.
[UPDATE]
It's clear that I already have all fomulated request(POST) text in my WebRequest instance.
Is there any way conveniently to just get the whole plain text from it, rather than using separated get properties such as ContentType or Headers?
(Because I am lazy to 'assemble' all the headers that I specified into the whole complete POST text, which web server will eventually see.)
// This might be a very poor code to approach.
public void Show_Full_POST_Text(WebRequest req)
{
// For example.
String strRawText = req.GetRequestLiteral();
// Your POST text looks like this!
ShowToUser(strRawText);
}
public void Foo()
{
// ...
Show_Full_POST_Text(req);
var ResponseStream = req.GetResponseStream();
// Continue.
}
If your saying that you don't want to get some headers via the request properties e.g "request.ContentType" and other via the header collection, then you can just use the header collection as it already contains the key for ContentType.
using System;
using System.Collections.Generic;
using System.Net;
namespace Demo
{
class Program
{
static void Main(string[] args)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://www.example.com");
request.Method = "POST";
request.ContentType = "image/png";
Console.WriteLine(GetRequestAsString(request));
Console.ReadKey();
}
public static string GetRequestAsString(HttpWebRequest request)
{
string str = request.Method + " " + request.RequestUri + " HTTP/" + request.ProtocolVersion + Environment.NewLine;
string[] headerKeys = request.Headers.AllKeys;
foreach (string key in headerKeys)
{
str += key + ":" + request.Headers[key];
}
return str;
}
}
}
Your question is pretty vague, however, I believe what you're looking for is including the headers within the Webrequest. The MSDN has a good example. See below:
public static void Main (string[] args)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create (args[0]);
// Set some reasonable limits on resources used by this request
request.MaximumAutomaticRedirections = 4;
request.MaximumResponseHeadersLength = 4; //This sets the max headers coming back to your response.
// Set credentials to use for this request.
request.Credentials = CredentialCache.DefaultCredentials;
HttpWebResponse response = (HttpWebResponse)request.GetResponse ();
Console.WriteLine ("Content length is {0}", response.ContentLength);
Console.WriteLine ("Content type is {0}", response.ContentType);
// Get the stream associated with the response.
Stream receiveStream = response.GetResponseStream ();
// Pipes the stream to a higher level stream reader with the required encoding format.
StreamReader readStream = new StreamReader (receiveStream, Encoding.UTF8);
Console.WriteLine ("Response stream received.");
Console.WriteLine (readStream.ReadToEnd ());
response.Close ();
readStream.Close ();
}
UPDATE: I figured it out and posted the answer below.
All I'm trying to do is update any file attribute. Description, name, anything, but no matter how I format it I get a 403.
I need to be able to modify a file so it can be shared via the Box API from a cloud app. I'm updating someone else's code from V1, but they are no longer available... I've tried many things but mostly just get 403 Forbidden errors.
There are no issues with OAuth2, that works fine and I can list files and folders, but can not modify them. This question is about sharing, but I can't change a description either. The box account is mine and I authenticate with my admin credentials. Any suggestions would be appreciated.
Here is the method I am using. I pass in the fileId and token and I've left out try/catch etc. for brevity.
string uri = string.Format("https://api.box.com/2.0/files/{0}", fileId);
string body = "{\"shared_link\": {\"access\": \"open\"}}";
byte[] postArray = Encoding.ASCII.GetBytes(body);
using (var client = new WebClient())
{
client.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
client.Headers.Add("Authorization: Bearer " + token);
var response = client.UploadData(uri, postArray);
var responseString = Encoding.Default.GetString(response);
}
Thanks.
Okay, My Homer Simpson moment...
UploadData is a POST, I needed to do a PUT. Here is the solution.
string uri = String.Format(UriFiles, fileId);
string response = string.Empty;
string body = "{\"shared_link\": {\"access\": \"open\"}}";
byte[] postArray = Encoding.ASCII.GetBytes(body);
try
{
using (var client = new WebClient())
{
client.Headers.Add("Authorization: Bearer " + token);
client.Headers.Add("Content-Type", "application/json");
response = client.UploadString(uri, "PUT", body);
}
}
catch (Exception ex)
{
return null;
}
return response;
try changing your content type to 'multipart/form-data'?
I just looked up the api at: https://developers.box.com/docs/#files-upload-a-file
and it looks like the server is expecting a multipart post
here is stack overflow post on posting multipart data:
ASP.NET WebApi: how to perform a multipart post with file upload using WebApi HttpClient
An issue we've faced since the beta launch of our software is that not all of our users are able to be authenticated (we authenticate using web requests) as a result of something happening. We're not sure exactly what is causing it, but we're slightly confident that it's caused by our POST requests. Below is the C# method we use to do the request.
public static string getResponse(string url, string postdata)
{
try
{
ASCIIEncoding encoding = new ASCIIEncoding();
byte[] byte1 = encoding.GetBytes(postdata);
HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);
myHttpWebRequest.Method = "POST";
myHttpWebRequest.ContentType = "application/x-www-form-urlencoded";
myHttpWebRequest.ContentLength = byte1.Length;
Stream newstream = myHttpWebRequest.GetRequestStream();
newstream.Write(byte1, 0, byte1.Length);
WebResponse response = myHttpWebRequest.GetResponse();
Stream stream = response.GetResponseStream();
StreamReader reader = new StreamReader(stream);
return reader.ReadToEnd();
}
catch
{
return "";
}
}
Perhaps we haven't structured it in a way that all firewalls will accept it, for example.
With that said, we do not know if it's the issue of the port, the request itself, or the user's issue. Has anyone ever faced this issue before? What did you do to fix it?
Are there "standards" to how you structure your POST so most devices/firewalls will accept it? Are we using the right port?
I have updated my catch statement to catch Web Exceptions:
catch (WebException ex)
{
ex.writeToDebuggerOrTxtFile;
}
Since at the moment we are not entirely sure about what is causing the issue, that is, we don't know if it's the user, firewall, or ports, that's causing the user being unable to authenticate, we have to first isolate the issue. If the status codes return something like HTTP 500, 401, 403, etc, then this indicates that the request is failing on the server.
EDIT: Upon sleeping over this, I saw a bit of an issue with this answer.
Let me explain.
Generally, all HTTP responses should succeed, even if the status code may return a code that indicates an error. I think the right way to approach this is to simply not just look at the WebExceptions thrown (since most "failed" responses should return a success), but to also look at purely the status codes.
A failed response would still succeed in bringing a response headers back, but the right way to approach it, I think, is to look at the status codes themselves.
Here's a node.js script I whipped up to check HTTP response headers:
var http = require("http");
var fs = require("fs");
var i = 0;
var hostNames = ['www.google.com'];
for (i; i < hostNames.length; i++){
var options = {
host: hostNames[i],
path: '/'
};
(function (i){
http.get(options, function(res) {
var obj = {};
obj.url = hostNames[i];
obj.statusCode = res.statusCode;
obj.headers = res.headers;
for(var item in res.headers){
obj.headers[item.replace(/\./,'\\')] = res.headers[item];
}
console.log(JSON.stringify(obj, null, 4));
}).on('error',function(e){
console.log("Error: " + hostNames[i] + "\n" + e.stack + "\n");
});
})(i);
};
Harvest is the time tracking application that I use at my job. While the web UI is quite simple, there are a few custom features I would like to add. I noticed they have an API... So I want to make a custom desktop client in C# for it.
Just looking at the page, its not very informative. The C# sample that you can find (after doing some digging) doesn't help much either. So... How in the world do I use the API with C#?
Link to API page
Any help would be greatly appreciated :)
Harvest is using a REST API, so what is does is you do a get/put/post request to a web address on the server and it will return a result (usually formatted in XML or JSON (appears to be XML in this case)). A quick Google search returned this tutorial on how to use a REST API, hopefully that will be enough for what you need. If not, feel free to ask us about specific problems you are having using REST and C#
Here I will try to add some more comments to their sample:
using System;
using System.Net;
using System.IO;
using System.Text;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
class HarvestSample
{
//This is used to validate the certificate the server gives you,
//it allays assumes the cert is valid.
public static bool Validator (object sender, X509Certificate certificate,
X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return true;
}
static void Main(string[] args)
{
//setting up the initial request.
HttpWebRequest request;
HttpWebResponse response = null;
StreamReader reader;
StringBuilder sbSource;
//1. Set some variables specific to your account.
//This is the URL that you will be doing your REST call against.
//Think of it as a function in normal library.
string uri = "https://yoursubdomain.harvestapp.com/projects";
string username="youremail#somewhere.com";
string password="yourharvestpassword";
string usernamePassword = username + ":" + password;
//This checks the SSL cert that the server will give us,
//the function is above this one.
ServicePointManager.ServerCertificateValidationCallback = Validator;
try
{
//more setup of the connection
request = WebRequest.Create(uri) as HttpWebRequest;
request.MaximumAutomaticRedirections = 1;
request.AllowAutoRedirect = true;
//2. It's important that both the Accept and ContentType headers
//are set in order for this to be interpreted as an API request.
request.Accept = "application/xml";
request.ContentType = "application/xml";
request.UserAgent = "harvest_api_sample.cs";
//3. Add the Basic Authentication header with username/password string.
request.Headers.Add("Authorization", "Basic " + Convert.
ToBase64String(new ASCIIEncoding().GetBytes(usernamePassword)));
//actually perform the GET request
using (response = request.GetResponse() as HttpWebResponse)
{
//Parse out the XML it returned.
if (request.HaveResponse == true && response != null)
{
reader = new StreamReader(response.GetResponseStream(),
Encoding.UTF8);
sbSource = new StringBuilder(reader.ReadToEnd());
//4. Print out the XML of all projects for this account.
Console.WriteLine(sbSource.ToString());
}
}
}
catch (WebException wex)
{
if (wex.Response != null)
{
using (HttpWebResponse errorResponse = (HttpWebResponse)wex.Response)
{
Console.WriteLine(
"The server returned '{0}' with the status code {1} ({2:d}).",
errorResponse.StatusDescription, errorResponse.StatusCode,
errorResponse.StatusCode);
}
}
else
{
Console.WriteLine( wex);
}
}
finally
{
if (response != null) { response.Close(); }
}
}
}
I've also struggled with their API. Scott's answer is very useful.
Anyway there is a very useful and easy library which is called EasyHttp witch you can find in NuGet.
here is the same method as Scott's but much shorter :):
public static string getProjects()
{
string uri = "https://<companyname>.harvestapp.com/projects";
HttpClient http = new HttpClient();
//Http Header
http.Request.Accept = HttpContentTypes.ApplicationJson;
http.Request.ContentType = HttpContentTypes.ApplicationJson;
http.Request.SetBasicAuthentication(username, password);
http.Request.ForceBasicAuth = true;
HttpResponse response = http.Get(uri);
return response.RawText;
}
If you want to learn more about WebApi calls you can use Fidler or a more easier and RestClient which is a Firefox plugin.
With RestClient you can talk to rest servers directly, very helpful if you want to understand RESTful services.