Recently we improved performance of one of our Ruby on Rails application. We used NewRelic tool to identify areas that were slow. One of which was file and image uploads to S3 from a text editor (Redactor).
When a user uploads a big file or image - the app's server response time shoots up degrading the performance and overall throughput because the files and images were being uploaded via rails action. The server process was busy uploading file on S3 while other requests to server were being queued increasing the request processing time. To improve this, we decided to delegate the job of file uploading to a new microservice reducing some load on our main server.
We created a microservice for uploading files and images and that brought us in front of another challenge.
The Redactor - our text editor, uploads the files and saves the response in an iframe. You can read the contents in an iframe easily via jQuery ($(iframe).contents()
) if the domain of the webpage which contains the iframe
is same as the domain of the web-page opened in iframe
.
The microservice responsible for uploading file was on different domain and the main app is on another domain. When we try to access the response in the iframe, we get security error - "Access denied: Cross-domain security error".
To access cross-domain iframe, the best approach is to use Javascript's postMessage()
method. This method provides a way to securely pass messages across domains.
Sending Messages with postMessage()
The postMessage()
method accepts two parameters.
message
- A string or object that will be sent to receiving window.targetOrigin
- The URL of the window the message is being sent to.
This method should be called on the window the message is being sent to.
targetWindow.postMessage("Hello !!", "http://example.com")
In case of iframes, the window can be obtained by accessing the contentWindow
property on the desired iframe.
Now lets receive messages sent to a window.
Setting up Event Listeners to Receive Messages
When a call to postMessage()
is executed successfully a MessageEvent
is fired on the receiving window. A standard event listener is enough to watch for this event and execute some code when it occurs.
To access the string or object that was sent by postMessage()
, data
property of the event passed into the listener callback can be used.
window.addEventListener('message', function(e) {
var message = e.data;
});
How we used in our app
Code in response of microservice -
<pre id="s3_response"><%= s3_response %></pre>
<script>
respondToMessage = function(event) {
if(event.origin == "http://our_domain.com"){
if (event.data == 's3_upload') {
var data = document.getElementById("s3_response").textContent;
window.parent.postMessage(data, event.origin);
}
}
}
window.addEventListener("message", respondToMessage, false);
</script>
Code in uploadLoaded
callback of Redactor in main app -
redactorUpload = function(e) {
// Check to make sure that this message came from the correct domain.
if (e.origin != "http://microservice_domain.com")
return;
// Get message received
var data = e.data;
postUploadProcessing(data);
// remove MessageEvent to prevent executing this function twice when another file is uploaded without refreshing the window
window.removeEventListener("message", redactorUpload, false)
}
window.addEventListener("message", redactorUpload, false);
var iframe = document.getElementById(this.id); // this.id gives id of iframe containing upload response
iframe.contentWindow.postMessage("s3_upload", microservice_upload_url);
Series of events happening sequentially explaining the flow -
- File is uploaded to S3 via microservice.
- The response from microservice is embedded inside iframe which is appended to
body
of main app initializing Event listener on iframe window. uploadLoaded
callback of redactor editor is executed (code mentioned above). Event listener initialised on main window.postMessage
method is called on iframe window.- iFrame event listener accepts the message, executes the function
respondToMessage
which sends S3 response to main window by callingpostMessage
method. - Event listener on main window listens to the
postMessage
method and executesredactorUpload
function.