I am looking to create a file on the fly and offer a download link to the user in a GRAILS application.
I followed the approach from here. I have no errors however it doesn't seem to work. Here's my controller code.
`render (file: pptFile, fileName:'someppt.pptx', contentType: 'application/octet-stream')
Client side code makes an AJAX call to retrieve the file from server. It does not cause the server to force downloading of the file on the client (browser). Here's the client side code.
$.ajax({
type : 'POST',
url : '<<URL>>',
success: function(result) {
var uri = 'data:application/octet-stream;charset=UTF-8,' +
encodeURIComponent(result);
window.open(uri, 'somePPT.pptx');
},
failure: function(){
alert ('failure')
}
});
Perhaps something akin to this (paraphrased, but used for downloading a json file):
def someControllerMethod() {
def dlContent = someService.marshalJson()
def contentType = "application/octet-stream"
def filename = "someFilename.json"
response.setHeader("Content-Disposition", "attachment;filename=${filename}")
render(contentType: contentType, text: dlContent as JSON)
}
okay. So I finally got this to work. As proposed by #railsdog and many others (This problem has been discussed on other threads in stackoverflow but the specific case I had was slightly different from those) I ended up writing to response directly from server and took out the AJAX call. The only reason I was doing an AJAX call was because I did not want to submit the current page that had the "generate file" functionality (There are many data elements on the page and I did not want to re-do the entire page just for downloading the file). So I ended up using an anchor tag with target as "_blank". Here's the code snippet
<a href="myControllerMethodToGenerateFileAndWriteToHTTPResponseDirectlyAsSuggestedByOthersInThisPost"
target="_blank"/>
This actually opened a new page and did the submission to initiate the download. Problem solved. It's working fine in CHROME. :) Thanks guys!
I like the solution using the render method from #railsdog !
A slightly other approach which I used so far was:
def controllerMethod() {
...
File file = sepaXmlService.createTransfersFile(...)
response.setContentType("application/xml")
response.setHeader("Content-disposition", "attachment;filename=${file.getName()}")
OutputStream out = response.getOutputStream()
out.write(file.bytes)
out.close()
file.delete()
return
...
}
In the view I use the following statement in the form:
<g:actionSubmit action="controllerMethod" class="btn" value="Get XML!" /></td>
I think it should also be possible to use a
<g:link controller="foobar" action="controllerMethod" class="btn">GetXML</g:link>
Related
Allowing POST method to an HTML page in ASP.NET MVC
I am using ASP.NET with MVC 5.2 and I am integrating RoxyFileManager to my CKEditor.
The integration was fine, the problem is when I try to upload some file to my web server, I got this error:
NetworkError: 405 Method Not Allowed - http://localhost:35418/FileManager/index.html?...
The RoxyFileManager uses the POST method to upload the file and my webserver does not accept it. I can't figure out how can I fix it.
If I put manually an image to my directory I can see it in the file manager, also I can create and exclude folders there.
To clarify my question: I want to know how can I make my webserver accept the POST method to a HTML page, just it. All the relevant information are above. I have a HTML page and want to make it accept POST.
#UPDATE:
I've figured out the problem is a browser issue.
In Google Chrome everything works fine;
In Firefox I get the error above;
In IE things seens to work fine, but it have cache problems (I can upload and edit previously sent files, but I can't see the changes neither the recent file uploads until cache expires);
I'll work on these problems and post the answer here, if successful.
To solve the IE bug it's simple but it's hard-work: You need to add in every ajax call of RoxyFileMan the line cache: false. You need to do it in every .js file on the RoxyFileMan folder.
Example:
$.ajax({
url: d, dataType: "json", async: true, success: function (h) {
for (i = 0; i < h.length; i++) { e.push(new File(h[i].p, h[i].s, h[i].t, h[i].w, h[i].h)) }
g.FilesLoaded(e)
},
error: function (h) { alert(t("E_LoadingAjax") + " " + d) },
cache: false
})
With this, all the ajax made by Roxy will have no cache, solving the IE issue.
To solve the Firefox bug I've changed this in the main.min.js:
BEFORE:
document.forms.addfile.action = RoxyFilemanConf.UPLOAD
AFTER:
$('form[name="addfile"]').attr('action', RoxyFilemanConf.UPLOAD);
I've found this solution here.
And now my file manager is working on all modern browsers.
I want to upload files with AJAX. In the past I accomplished this by using the magical jQuery form plugin and it worked great. Currently I'm building a Rails app and trying to do things "The Rails Way" so I'm using the Form Helper and the paperclip gem to add file attachments.
The rails docs warn that the Form Helper does not work for AJAX file uploads:
Unlike other forms making an asynchronous file upload form is not as
simple as providing form_for with remote: true. With an Ajax form the
serialization is done by JavaScript running inside the browser and
since JavaScript cannot read files from your hard drive the file
cannot be uploaded. The most common workaround is to use an invisible
iframe that serves as the target for the form submission.
It seems clear there's no off-the-shelf solution. So I'm wondering what's the smartest thing to do. Seems like I have several options:
Use the form helper and the iframe trick.
Use the form helper + load jQuery form plugin to submit the file (not sure if this will play nice with Rails's authenticity token, etc)
Use the form helper + paperclip + [some other gem] to extend it's functionality to allow AJAX form submission.
All three seem possible. I know the least about #3, specifically the [some other gem] part. I found two similar questions (this and this) which mention a branch of Pic-Upload called Uploadify but those are both 2 years old and deal with Rails 2 and 3 (and Uploadify hasn't been updated in years). So given how much has changed, I think this is really a whole new question:
What's the best way to upload files with AJAX in Rails 4?
Have a look into the remotipart gem: https://github.com/JangoSteve/remotipart -- may get you all of the way there with very little work!
Using #rails/ujs.
view (.html.erb):
<%= file_field_tag :file, { id: "ajax_file_upload"} %>
controller(_controller.rb):
def update
#record = YourModel.find(params[:id])
respond_to do |format|
if #record.update_attributes(params[:your_model])
format.json { render json: { success: true } }
else
error_messages = #record.errors.messages.values.flatten
format.json { render json: { success: false, errors: error_messages } }
end
end
end
javascript(.js)
const uploadFile = element => {
const formData = new FormData();
formData.append("your_model[attribute_name]", element.target.files[0]);
Rails.ajax({
url: "your_model/:id",
type: "PUT",
beforeSend(xhr, options) {
options.data = formData;
return true;
},
success: response => {
if (response.success) {
alert("File uploaded successfully");
}
else {
alert(response.errors.join("<br>"));
}
},
error: () => {
alert("ajax send error");
}
});
};
const documentOnReady = () => {
const fileField = document.getElementById("ajax_file_upload");
if (fileField) {
fileField.addEventListener("change", uploadFile);
}
}
document.addEventListener("turbolinks:load", documentOnReady);
Note: No need to setRequestHeader in ajax while using FormData.
FormData uses the same format a form would use if the encoding type were set to "multipart/form-data"
IMHO Rails is not perfect when dealing with upload files using AJAX, especially if you want a progress bar. My suggestion is to use Javascript for the form submission over an AJAX request like you suggested in (2). If you are comfortable with Javascript you will not have many problems.
I recently used the same approach by using this very simple JS library https://github.com/hayageek/jquery-upload-file and I wrote more details here http://www.alfredo.motta.name/upload-video-files-with-rails-paperclip-and-jquery-upload-file/
For an application with a form to upload a movie with title and description the JS code looks like follow:
$(document).ready(function() {
var uploadObj = $("#movie_video").uploadFile({
url: "/movies",
multiple: false,
fileName: "movie[video]",
autoSubmit: false,
formData: {
"movie[title]": $('#movie_title').text(),
"movie[description]": $('#movie_description').text()
},
onSuccess:function(files,data,xhr)
{
window.location.href = data.to;
}
});
$("#fileUpload").click(function(e) {
e.preventDefault();
$.rails.disableFormElements($($.rails.formSubmitSelector));
uploadObj.startUpload();
});
});
Far from perfect, but gives you flexibility on your frontend.
Following the tutorial found on ASP.NET, implemented a Web API controller method for doing asynchronous file uploads that looks like this:
public Task<HttpResponseMessage> PostFormData()
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
// Read the form data and return an async task.
var task = Request.Content.ReadAsMultipartAsync(provider).
ContinueWith<HttpResponseMessage>(t =>
{
if (t.IsFaulted || t.IsCanceled)
{
Request.CreateErrorResponse(HttpStatusCode.InternalServerError, t.Exception);
}
return Request.CreateResponse(HttpStatusCode.OK);
});
return task;
}
Uploading a file via a standard multipart HTML form works perfectly. However, when another developer attempts to upload a file via multipart form constructed by Flex's FileReference class, an error is thrown:
Unexpected end of MIME multipart stream. MIME multipart message is not complete.
I have no idea if the problem lies in Web API or Flex. I've found some sort of related fixes that had no affect (Multipart form POST using ASP.Net Web API), and more recently this one ("MIME multipart stream. MIME multipart message is not complete" error on webapi upload). If the second link holds true, does anyone know if it's out in the current release of Web API available via Nuget? The discussion was in May, the most recent release from Nuget was August, so I assume this fix was deployed already, and is not the root cause of my issue.
I had the same problem with MVC4, but Will is correct, add a name to your input.....
<input type="file" id="fileInput" name="fileInput"/>
and all the magic is back up and working!
I had the same problem with flex. And below is the code that solved it. Basically I used a custom stream to append the newline that asp.net web api is expecting.
Stream reqStream = Request.Content.ReadAsStreamAsync().Result;
MemoryStream tempStream = new MemoryStream();
reqStream.CopyTo(tempStream);
tempStream.Seek(0, SeekOrigin.End);
StreamWriter writer = new StreamWriter(tempStream);
writer.WriteLine();
writer.Flush();
tempStream.Position = 0;
StreamContent streamContent = new StreamContent(tempStream);
foreach(var header in Request.Content.Headers)
{
streamContent.Headers.Add(header.Key, header.Value);
}
// Read the form data and return an async task.
await streamContent.ReadAsMultipartAsync(provider);
Hope this helps.
Reading through your existing research and following through to the codeplex issue reported it looks like someone else confirmed this issue to still exist in September.
They believe that MVC 4 fails to parse uploads without a terminating "\r\n".
The issue is really simple but extremely hard to fix. The problem is that Uploadify does > not add an "\r\n" at the end of the MultiPartForm message
http://aspnetwebstack.codeplex.com/discussions/354215
It may be worth checking that the Flex upload adds the "\r\n"
For those landing here googling:
Unexpected end of MIME multipart stream. MIME multipart message is not complete.
Reading the request stream more than once will also cause this exception. I struggled with it for hours until I found a source explaining that the request stream only could be read once.
In my case, I combined trying to read the request stream using a MultipartMemoryStreamProvider and at the same time letting ASP.NET do some magic for me by specifying parameters (coming from the request body) for my api method.
Make sure the virtual directory ("~/App_Data" directory as below example) where the image files are first uploaded are physically existance. When you publish the project, it may not be in the output files.
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
I just removed my headers I was setting on my post method which ended up solving this issue.
The problem is this line:
string root = HttpContext.Current.Server.MapPath("~/App_Data");
It will only work in localhost, you can use HostingEnvironment.MapPath instead in any context where System.Web objects like HttpContext.Current are not available (e.g also from a static method).
var mappedPath = System.Web.Hosting.HostingEnvironment.MapPath("~/SomePath");
See also What is the difference between Server.MapPath and HostingEnvironment.MapPath?
Reference to this answer How to do a Server Map Path.
My main page is here:
http://www.mydomain.com/main/main.php
My login page is here:
http://www.mydomain.com/main/pages/login.php
Main.php uses ajax to fetch data in response to a tap event. This works fine until I navigate to my login page and then back to my main page. After going to the login page and back, the relative paths get messed up such that the ajax looks for server file in the wrong place.
here is the ajax:
1. function get_more_data() {
2. more_data_index += 15;
3. var formData = "index=" + more_data_index;
4. $.ajax({
5. type: "POST",
6. url: "genxml.php", // file located here: http://www.mydomain.com/main/genxml.php
7. cache: false,
8. data: formData,
9. dataType: "xml",
10. success: showFiles3,
11. error: onErrorMoreData
12. });
13. }
After I navigate back to main.php from login.php the ajax tries posting to the wrong location:
http://www.mydomain.com/main/pages/genxml.php
(genxml.php is not in the "pages" subdirectory; it's in the main directory.)
I tried updating the ajax to use an absolute path:
url: "http://www.mydomain.com/main/genxml.php"
This made the post successful, but my data parsing failed because relatives paths are used in the main file for things like images. So instead of getting images from here: http://www.mydomain.com/main/ the script was trying to get images from here: http://www.mydomain.com/main/pages/
I've found a few posts with people having similar issues, but I've not come across a solution. I've also tried reading the jquerymobile docs and it's very possible that the jquery developers attempt to cover this issue here, but I admit I don't completely understand everything on this page:
http://jquerymobile.com/demos/1.0b3/#/demos/1.0b3/docs/pages/page-navmodel.html
If anyone can help I would really appreciate it. Thanks.
P.S. This issue happens on Android and Google Chrome, but not in Firefox.
I have created a working example of what you're trying to do. You should be able to look at this and see what I've done. Be sure to checkout the master.js. I think that the key to making it work in your situation is to nest the ajax calls within the "pageshow" event to be sure that your baseURL has been updated. You can download the example at http://www.roughlybrilliant.com/stackoverflow/7372909.7z
View the example in action as it pulls in weather.xml with relative URLs.
$("div").live("pageshow", function(){
var $page = $(this);
get_more_data();
});
Why don't you use one of this:
use "../main.php" when redirecting back from login page, or
remember UrlRefer from Headers when you entering login.php and use that to redirect back to any previous page with 301
I want to prefix URLs which match my patterns. When I open a new tab in Firefox and enter a matching URL the page should not be loaded normally, the URL should first be modified and then loading the page should start.
Is it possible to modify an URL through a Mozilla Firefox Addon before the page starts loading?
Browsing the HTTPS Everywhere add-on suggests the following steps:
Register an observer for the "http-on-modify-request" observer topic with nsIObserverService
Proceed if the subject of your observer notification is an instance of nsIHttpChannel and subject.URI.spec (the URL) matches your criteria
Create a new nsIStandardURL
Create a new nsIHttpChannel
Replace the old channel with the new. The code for doing this in HTTPS Everywhere is quite dense and probably much more than you need. I'd suggest starting with chrome/content/IOUtils.js.
Note that you should register a single "http-on-modify-request" observer for your entire application, which means you should put it in an XPCOM component (see HTTPS Everywhere for an example).
The following articles do not solve your problem directly, but they do contain a lot of sample code that you might find helpful:
https://developer.mozilla.org/en/Setting_HTTP_request_headers
https://developer.mozilla.org/en/XUL_School/Intercepting_Page_Loads
Thanks to Iwburk, I have been able to do this.
We can do this my overriding the nsiHttpChannel with a new one, doing this is slightly complicated but luckily the add-on https-everywhere implements this to force a https connection.
https-everywhere's source code is available here
Most of the code needed for this is in the files
IO Util.js
ChannelReplacement.js
We can work with the above files alone provided we have the basic variables like Cc,Ci set up and the function xpcom_generateQI defined.
var httpRequestObserver =
{
observe: function(subject, topic, data) {
if (topic == "http-on-modify-request") {
var httpChannel = subject.QueryInterface(Components.interfaces.nsIHttpChannel);
var requestURL = subject.URI.spec;
if(isToBeReplaced(requestURL)) {
var newURL = getURL(requestURL);
ChannelReplacement.runWhenPending(subject, function() {
var cr = new ChannelReplacement(subject, ch);
cr.replace(true,null);
cr.open();
});
}
}
},
get observerService() {
return Components.classes["#mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
},
register: function() {
this.observerService.addObserver(this, "http-on-modify-request", false);
},
unregister: function() {
this.observerService.removeObserver(this, "http-on-modify-request");
}
};
httpRequestObserver.register();
The code will replace the request not redirect.
While I have tested the above code well enough, I am not sure about its implementation. As far I can make out, it copies all the attributes of the requested channel and sets them to the channel to be overridden. After which somehow the output requested by original request is supplied using the new channel.
P.S. I had seen a SO post in which this approach was suggested.
You could listen for the page load event or maybe the DOMContentLoaded event instead. Or you can make an nsIURIContentListener but that's probably more complicated.
Is it possible to modify an URL through a Mozilla Firefox Addon before the page starts loading?
YES it is possible.
Use page-mod of the Addon-SDK by setting contentScriptWhen: "start"
Then after completely preventing the document from getting parsed you can either
fetch a different document from the same domain and inject it in the page.
after some document.URL processing do a location.replace() call
Here is an example of doing 1. https://stackoverflow.com/a/36097573/6085033