I need to rewrite not redirect certain requests to GET files to an external storage location (hosted on a different domain)
I am able to accomplish this with redirecting using rewriter middleware, however i would like to rewrite instead.
I need to use some logic to rewrite to a specific file location, so I followed https://learn.microsoft.com/en-us/aspnet/core/fundamentals/url-rewriting?view=aspnetcore-2.0&tabs=aspnetcore2x#method-based-rule tutorial.
When i set the Host and path i can see the absolute url looks correct but when the method exits i get "No webpage was found for the web address:" and url displayed is the original request Url
RewriteRule.cs (also holds redirect url logic in ApplyRule() that i don't want to use)
public static void RewriteStorageFileRequests(RewriteContext context)
{
var request = context.HttpContext.Request;
var path = request.Path.Value;
if (path.Contains("foo/bar"))
{
context.Result = RuleResult.SkipRemainingRules;
request.Host = new HostString("storage/space");
request.Path = path.Replace("foo/bar", string.Empty);
var test2 = request.GetDisplayUrl();
}
}
So essentially, if a call comes in as:
https://localhost:44327/foo/bar/bf34d911-03db-5tda-9c99-c7dd96593159/w3.jpg
I want to rewrite it to:
https://storage/space/bf34d911-03db-5tda-9c99-c7dd96593159/w3.jpg
Startup.cs
app.UseRewriter(new RewriteOptions().Add(RewriteUrlRule.RewriteStorageFileRequests));
test2 displays the correct url in the debugger.
I tried setting host to empty string and passing host value (storage/space) in path but still no luck.
Before Rewriter
After Rewrite
UPDATE: I was able to get rewrite to work by changing
context.Result = RuleResult.SkipRemainingRules;
to
context.Result = RuleResult.EndResponse;
However, when i get to the Url it returns 200OK but does not show the image.
(see attached file)
So, right now the only way i can get images is still with Redirect
Rewrite GET
Related
I have an API proxy created with Apigee which works properly. However, I want to make a change in a resource name.
Currently, I have the following resource:
<proxy>/collection/{Id}/products which redirects to <myService>/collection/{Id}/products
I want to rename the proxy resource like this:
<proxy>/collection/{Id}/apps which redirects to <myService>/collection/{Id}/products
What is the best way to to that with Apigee?
Cheers,
Chavdar
If you'd like to proxy requests for /collection/{id}/apps to /collection/{id}/products, add a JavaScript policy to the target request with the following snippet of code:
var collection_id = 10; //you should get this using context.getVariable()
var path = '/collection/' + collection_id + '/products'; //new path
var current_target_url = context.getVariable('target.url'); //get the existing target url
context.setVariable('target.copy.pathsuffix', false); //tell apigee to not copy the path over to the target
context.setVariable('debug.js.path', current_target_url + path); //this line is only for seeing the value in the trace tool
context.setVariable('target.url', current_target_url + path); //set the new path
I have created following route
routes.MapRoute("ThumbnailRoute",// Route name
"Image/{action}/{session}/{parentId}/{fileName}/{ctype}/{thumbNailSize}", // URL with parameters
new { controller = "Image", action = "GenerateThumbnail", session = "", parentId = "", fileName = "", ctype = "", thumbNailSize = 70 }, // Parameter defaults
new { controller = #"[^\.]*", action = #"[^\.]*" });
and my extension method returns a string like following which will be the src attribute of the img tag:
return string.Format("/{0}/{1}/{2}/{3}/{4}/{5}/{6}", controller, action, session, parentId, fileName, ctype, thumbNailSize);
when I right click on the pages and choose properties for both dev and prod environments the src av img tag is same (http://localhost/Image/GenerateThumbnail/de-DE/121/0beac6da-7c09-4faf-ad4b-48326f9d337e.jpg/jpeg/70) only different is the domain name (localhost, www.domain.com) but the images de not appear on prod. thanks for your help
If the URLs look fine on the production version - perhaps the issue isn't with the routing, but rather with the code in the action method.
Have you checked what response you get from the browser when hitting the production URL?
What response do you get when your browse to http://www.domain.com/Image/GenerateThumbnail/de-DE/121/0beac6da-7c09-4faf-ad4b-48326f9d337e.jpg/jpeg/70?
Never hardcode urls as you did. Always use url helpers. The thing is that when you deploy your application in IIS there's a virtual directory name. So the correct url is the following:
http://foo.com/MyAppName/Image/GenerateThumbnail/de-DE/121/0beac6da-7c09-4faf-ad4b-48326f9d337e.jpg/jpeg/70
instead of:
http://foo.com/Image/GenerateThumbnail/de-DE/121/0beac6da-7c09-4faf-ad4b-48326f9d337e.jpg/jpeg/70
Since you have hardcoded the url you get 404.
So use the RouteUrl method to generate it which will take into account this virtual directory if any. Don't use any string formatting to build urls:
public ActionResult Index()
{
string url = Url.RouteUrl("ThumbnailRoute", new
{
action = "GenerateThumbnail",
controller = "Image",
session = session,
parentId = parentId,
fileName = fileName,
ctype = ctype,
thumbNailSize = thumbNailSize
});
...
}
We download a file from our CdN and then return a url to that downloaded file to the user. I'm trying to get this implemented so that when a user clicks the download buttton, it goes and test that url to that downloaded file then forces a save prompt based on that local url.
So for example if there is a button called download on the page for a specific .pdf, we ultimately have code in our controller going to the cdn and downloading the file, zipping it then returning a url such as: http://www.ourLocalAssetServer.com/assets/20120331002728.zip
I'm not sure if you you can use the File() method to return the resource to the user as to cause a save prompt when you have a url to the file, not a system directory virtual path.
So how can I get this working with the url? I need the download button to ultimately force a save prompt on their end given a url such as what is generated per this example above? Not I am using POST, not a GET, so not sure which I should use in this case either besides this not working overall to force a save prompt. It is hitting my GetFileDownloadUrl but ultimately errors saying it's not a virtual path.
Here's my code:
#foreach (CarFileContent fileContent in ModelCarFiles)
{
using (Html.BeginForm("GetFileDownloadUrl", "Car", FormMethod.Get, new { carId = Model.CarId, userId = Model.UserId, #fileCdnUrl = fileContent.CdnUrl }))
{
#Html.Hidden("userId", Model.UserId);
#Html.Hidden("carId", Model.CarId);
#Html.Hidden("fileCdnUrl", fileContent.CdnUrl);
<p><input type="submit" name="SubmitCommand" value="download" /> #fileContent.Name</p>
}
}
public ActionResult GetFileDownloadUrl(string fileCdnUrl, int carId, int userId)
{
string downloadUrl = string.Empty;
// take the passed Cdn Url and go and download that file to one of our other servers so the user can download that .zip file
downloadUrl = GetFileZipDownloadUrl(carId, userId, fileCdnUrl;
// now we have that url to the downloaded zip file e.g. http://www.ourLocalAssetServer.com/assets/20120331002728.zip
int i = downloadUrl.LastIndexOf("/");
string fileName = downloadUrl.Substring(i);
return File(downloadUrl, "application/zip", fileName);
}
error: not a valid virtual path
This won't work except the zip file is in your virtual path.
The File method you have used here File(string, string, string) expects a fileName which will be used to create a FilePathResult.
Another option would be to download it (using WebClient.DownloadData or DownloadFile methods) and passing either the byte array or the file path (depending on which you choose).
var webClient = new Webclient();
byte[] fileData = webClient.DownloadData(downloadUrl);
return File(fileData, "application/zip", fileName);
And the lines where you get the index of "/" just to get the filename is unnecessary as you could have used:
string fileName = System.IO.Path.GetFileName(downloadUrl);
I have a simple implementation of custom protocol. It's said that newURI method takes 3 arguments (spec, charset & baseURI) and "if the protocol has no concept of relative URIs, third parameter is ignored".
So i open a page like this tada://domain/samplepage which has XML starting with this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Product SYSTEM "product.dtd">
But i don't see any request regarding product.dtd to my protocol (newURI is not even called). Do i miss smth in my implementation?
BTW: the page itself opens correctly, but there's no request to the DTD-file.
const
Cc = Components.classes,
Ci = Components.interfaces,
Cr = Components.results,
Cu = Components.utils,
nsIProtocolHandler = Ci.nsIProtocolHandler;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
function TadaProtocol() {
}
TadaProtocol.prototype = {
scheme: "tada",
protocolFlags: nsIProtocolHandler.URI_DANGEROUS_TO_LOAD,
newURI: function(aSpec, aOriginCharset, aBaseURI) {
let uri = Cc["#mozilla.org/network/simple-uri;1"].createInstance(Ci.nsIURI);
uri.spec = (aBaseURI === null)
? aSpec
: aBaseURI.resolve(aSpec);
return uri;
},
newChannel: function(aURI) {
let
ioService = Cc["#mozilla.org/network/io-service;1"].getService(Ci.nsIIOService),
uri = ioService.newURI("chrome://my-extension/content/about/product.xml", null, null);
return ioService.newChannelFromURI(uri);
},
classDescription: "Sample Protocol Handler",
contractID: "#mozilla.org/network/protocol;1?name=tada",
classID: Components.ID('{1BC90DA3-5450-4FAF-B6FF-F110BB73A5EB}'),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler])
}
let NSGetFactory = XPCOMUtils.generateNSGetFactory([TadaProtocol]);
The channel you return from newChannel has the chrome:// URI you passed to newChannelFromURI as its URI. So that's the URI the page has as its URI, and as its base URI. So the DTD load happens from "chrome://my-extension/content/about/product.dtd" directly.
What you probably want to do is to set aURI as the originalURI on the channel you return from newChannel.
As Boris mentioned in his answer, your protocol implementation doesn't set nsIChannel.originalURI property so that URLs will be resolved relative to the chrome: URL and not relative to your tada: URL. There is a second issue with your code however: in Firefox loading external DTDs only works with chrome: URLs, this check is hardcoded. There is a limited number of supported DTDs that are mapped to local files (various HTML doctypes) but that's it - Gecko doesn't support random URLs in <!DOCTYPE>. You can see the current logic in the source code. The relevant bug is bug 22942 which isn't going to be fixed.
Boris and Wladimir, thank you!
After some time i have a solution. The problem was that the DTD-file could not be loaded from my custom-created protocol. The idea was to use Proxy API to override schemeIs() method, which was called in newURI method of nsIProtocolHandler.
So now i have this snippet of code in newURI method:
let standardUrl = Cc["#mozilla.org/network/standard-url;1"].createInstance(Ci.nsIStandardURL);
standardUrl.init(standardUrl.URLTYPE_STANDARD, -1, spec, charset, baseURI);
standardUrl.QueryInterface(Ci.nsIURL);
return Proxy.create(proxyHandlerMaker(standardUrl));
proxyHandlerMaker just implements Proxy API and overrides the needed schemeIs() method. This solved the problem and now all the requests come to newChannel where we can handle them.
Important notes:
Request to DTD comes to newURI() method and does not come to newChannel(). This is the default behavior. This happens because schemeIs("chrome") method is called on the object which was returned by newURI() method. This method should return "true" for DTD-requests if you want the request to reach the newChannel() method.
newChannel() method is invoked with the {nsIURI} object which is not the same as the object which was returned by the newURI method.
If you want to handle both protocol:page & protocol://domain/page URLs by your protocol, you should use both {nsIURI} and {nsIStandardURL} objects
You can pass the created {nsIStandardUrl}-object (standardUrl in the snippet above) as a 2nd argument to the Proxy.create() function. This will make your baseURI (3rd arguments in newURI) pass "baseURI instanceof nsIStandardUrl" check. SchemeIs() method of this proxied object will also return true for the DTD-files requests. But unfortunately the requests won't reach newChannel() method. This could be a nice DTD-problem solution but I can't solve this problem.
I am using the Background Transfer to upload Photographs to my Web Service. As the Photograph uploads can consume significant time and memory, I thought it might be a nice idea to use the background transfer request to accomplish this. After the photo is uploaded, I want to obtain the Id of the uploaded photo and then use it for post-processing. However, it turns out I can't do that in a background transfer request.
Per my understanding, Background Transfer works using the following logic ONLY:
You have to obtain the file you want to upload and then save/copy it to your app's Isolated Storage under the folder: shared/transfers. This is extremely important. Apparently, using file in a different location didn't work for me. Maybe it isn't the shared/transfers as much as it is a 'relative' path. But I would stick to the same conventions.
After you have saved the file in that location, your background request can be created based on that. It doesn't look like you can pass POST CONTENT other than the file contents, so any other parameters like file name, mime type etc. will need to be passed as QUERY String parameters only. I can understand this, but it would've been nice if I could pass both as POST Content. I don't think HTTP has a limitation on how this works.
Here is some code for creating a request using Hammock:
string url = App.ZineServiceAuthority + "articles/save-blob?ContainerName={0}&MimeType={1}&ZineId={2}&Notes={3}&IsPrivate={4}&FileName={5}";
url = String.Format(url, userId, "image/jpg", ZineId, txtStatus.Text, true, UploadFileName);
var btr = new BackgroundTransferRequest(new Uri(url, UriKind.Absolute));
btr.TransferPreferences = TransferPreferences.AllowCellularAndBattery;
btr.Method = "POST";
btr.Headers.Add("token", IsolatedStorageHelper.GetTravzineToken());
btr.UploadLocation = new Uri(#"/shared\transfers/" + UploadFileName, UriKind.Relative);
btr.TransferStatusChanged += new EventHandler<BackgroundTransferEventArgs>(btr_TransferStatusChanged);
btr.TransferProgressChanged += new EventHandler<BackgroundTransferEventArgs>(btr_TransferProgressChanged);
BackgroundTransferService.Add(btr);
In my case, I am literally passing all the necessary parameters using the query string. On a successful save, my Web Service returns back the Id of the Photo I just uploaded. However:
There is NO way (or at least I know of) to obtain and evaluate the RESPONSE. The Background Transfer Request Event handlers do not expose a RESPONSE.
Here are my event handlers:
void btr_TransferProgressChanged(object sender, BackgroundTransferEventArgs e)
{
bool isUploading = e.Request.TotalBytesToSend > 0 ? true : false;
lblStatus.Text = isUploading ? "Uploading" + e.Request.BytesSent.ToString() + " sent" : "Done";
}
void btr_TransferStatusChanged(object sender, BackgroundTransferEventArgs e)
{
if (e.Request.TransferStatus == TransferStatus.Completed)
{
using (IsolatedStorageFile iso =
IsolatedStorageFile.GetUserStoreForApplication())
{
if (iso.FileExists(e.Request.UploadLocation.OriginalString))
iso.DeleteFile(e.Request.UploadLocation.OriginalString);
}
BackgroundTransferService.Remove(e.Request);
if (null != e.Request.TransferError)
{
MessageBox.Show(e.Request.TransferError.Message);
}
else
{
lblStatus.Text = "Done baby done";
}
}
}
So now my question is, how does anyone do any sort of POST Processing in such scenarios?
Can anyone please tell me the line of thought behind designing such an inflexible class?
Any thoughts on how I could get around this issue would be appreciated.
Also, does anyone have any working examples of a homegrown BackgroundTransfer?
Haven't tried it but why not set a download location like this:
btr.DownloadLocation = "myDownloadFile.html";
btr.UploadLocation = "myUploadFile.jpg";
...
If the request is completed read the file "myDownloadFile.html" where your response has been stored and delete it afterwards.