Unable to set Content-Type Response Header in Cakephp 3.2 - response

I'm having difficulty setting the Content-Type response header using the Cake\Network\Response::header method as outlined here: http://book.cakephp.org/3.0/en/controllers/request-response.html#setting-headers
Essentially, I'm able to set just about any header (even random made up ones), except for the Content-Type header. This leads me to believe there is somewhere else in the core application that is writing the Content-Type header AFTER my controller method has completed.
Here's some code:
Controller:
public function sseTest() {
$this->response->header([
'Cache-Control' => 'no-cache',
'Content-Type' => 'text/event-stream'
]);
$this->viewBuilder()->layout('sse');
$this->set('data', date("r"));
}
Template (sse_test.ctp):
data: <?= $data ?>
Layout (sse.ctp):
<?= $this->fetch('content') ?>
And finally, the AppController:
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* #copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* #link http://cakephp.org CakePHP(tm) Project
* #since 0.2.9
* #license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace App\Controller;
use Cake\Controller\Controller;
use Cake\Event\Event;
/**
* Application Controller
*
* Add your application-wide methods in the class below, your controllers
* will inherit them.
*
* #link http://book.cakephp.org/3.0/en/controllers.html#the-app-controller
*/
class AppController extends Controller
{
/**
* Initialization hook method.
*
* Use this method to add common initialization code like loading components.
*
* e.g. `$this->loadComponent('Security');`
*
* #return void
*/
public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler');
$this->loadComponent('Flash');
}
/**
* Before render callback.
*
* #param \Cake\Event\Event $event The beforeRender event.
* #return void
*/
public function beforeRender(Event $event)
{
if (!array_key_exists('_serialize', $this->viewVars) &&
in_array($this->response->type(), ['application/json', 'application/xml'])
) {
$this->set('_serialize', true);
}
}
}
As you can see, I've got nothing funky in the AppController, and I've dumbed my controller method down to be very basic and just return a formatted date. When I access this page by going to app.com/controller/sse-test I get a page that is exactly what I would expect:
data: Thu, 30 Jun 2016 01:18:03 +0000
The issue is that when I use Chrome Dev Tools to look at the response headers, this is what I have:
HTTP/1.1 200 OK
Date: Thu, 30 Jun 2016 01:18:03 GMT
Server: Apache/2.4.7 (Ubuntu)
X-Powered-By: PHP/5.5.9-1ubuntu4.14
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-cache
Pragma: no-cache
X-DEBUGKIT-ID: 511a91a6-c2f7-47b6-85bb-39644994e0dd
Content-Length: 37
Keep-Alive: timeout=5, max=17
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
As you can see, the Cache-Control header is set, but then the Content-Type header isn't what I specified. I'm fairly certain it's being overwritten, but I just don't know where. Do any of you have some suggestions as to where I should look or why this might be happening?
For grins, I made a standalone PHP page like below:
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
$time = date('r');
echo "data: The server time is: {$time}\n\n";
flush();
?>
This page works flawlessly supplying server sent events to a static html page, which is ultimately my goal...but I'd like to use CakePHP framework...
I'm sure I'm missing something simple, please let me know your thoughts. Thank you.
Per the first comment below, I looked a little further up the page at "Dealing with Content Types". I've modified my controller to be:
public function sseTest() {
$this->response->header([
'Cache-Control' => 'no-cache'
]);
//add text/event-stream type
$this->response->type('text/event-stream');
$this->viewBuilder()->layout('sse');
$this->set('data', date("r"));
}
I've even taken a look at the response->_mimeTypes variable in the Response class, my text/event-stream is being added. If I simply print out the results of $this->response->type() after setting the content type, it returns text/event-stream. But when the response is set, the actual header still includes Content-Type: text/html; charset=UTF-8. Any further ideas?

Related

gzip compression doesn't work and can't get 304 in chrome

I'm working on a compression and caching mechanism in my asp.net mvc 5 app.
I'm sending files with the following cache headers:
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Cache.SetExpires(DateTime.UtcNow.AddYears(1).ToUniversalTime());
Response.Cache.SetLastModified(System.IO.File.GetLastWriteTime(serverPath).ToUniversalTime());
Response.AppendHeader("Vary", "Accept-Encoding");
IE11, Edge, Firefox, all sends the If-Modified-Since header on F5 refresh, but not Chrome. Why is that and how to workaround it? In Chrome I got 200 status code and file is loaded from cache.
The second problem I have is with enabling gzip compression.
I have a standard action filter for this:
public class CompressContentMvcAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
GZipEncodePage();
}
private bool IsGZipSupported()
{
string AcceptEncoding = HttpContext.Current.Request.Headers["Accept-Encoding"];
if (!string.IsNullOrEmpty(AcceptEncoding) &&
(AcceptEncoding.Contains("gzip") || AcceptEncoding.Contains("deflate")))
{
return true;
}
return false;
}
private void GZipEncodePage()
{
HttpResponse Response = HttpContext.Current.Response;
if (IsGZipSupported())
{
string AcceptEncoding = HttpContext.Current.Request.Headers["Accept-Encoding"];
if (AcceptEncoding.Contains("gzip"))
{
Response.Filter = //new GZipCompressionService().CreateCompressionStream(Response.Filter);
new System.IO.Compression.GZipStream(Response.Filter,
System.IO.Compression.CompressionMode.Compress);
Response.Headers.Remove("Content-Encoding");
Response.AppendHeader("Content-Encoding", "gzip");
}
else
{
Response.Filter =// new DeflateCompressionService().CreateCompressionStream(Response.Filter);
new System.IO.Compression.DeflateStream(Response.Filter,
System.IO.Compression.CompressionMode.Compress);
Response.Headers.Remove("Content-Encoding");
Response.AppendHeader("Content-Encoding", "deflate");
}
}
// Allow proxy servers to cache encoded and unencoded versions separately
Response.AppendHeader("Vary", "Content-Encoding");
}
}
I apply this filter on my action method returning application assets, but it got the Transfer-Encoding: chunked for each file, not gziped.
This filter is copied from my previous project and there it is still working at is expected. Could it be a problem with IIS server? Locally I have a IIS 10 and .NET 4.7, the the older app, where it works is hosted on IIS 8.5 and framework 4.5. Can't think of anything else. I'm googling the second day and can't find any clue.
I'm not interested in compressing in IIS.
[edit]
Header I got from response:
HTTP/1.1 200 OK
Cache-Control: public
Content-Type: text/javascript
Expires: Sat, 18 May 2019 08:58:48 GMT
Last-Modified: Thu, 10 May 2018 13:26:02 GMT
Vary: Content-Encoding
Server: Microsoft-IIS/10.0
X-AspNetMvc-Version: 5.2
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 18 May 2018 08:58:48 GMT
Transfer-Encoding: chunked
I always use Fiddler to inspect these kind of challenges.
The F5/If-Modified-Since issue.
Chrome just doesn't make a new request if the expires header has been set and its datetime value is still actual. So Chrome is respecting your expected caching behaviour. When navigating over your website via an other browser, you 'll see that also these don't send any requests for these assets. F5 is 'special', it's a forced refresh.
The chunked/gzip issue
Clear your browser cache and inspect the first response. Fiddler will show 'Response body is encoded', which means compressed (gzip or deflate).
Whether you see Transfer-Encoding chunked depends on whether the Content-Length header is present. See the difference in the responses below. If you don't want the chunked Transfer-Encoding set the Content-Length header.
Content-Type: text/javascript; charset=utf-8
Content-Encoding: gzip
Expires: Sat, 25 May 2019 13:14:11 GMT
Last-Modified: Fri, 25 May 2018 13:14:11 GMT
Vary: Accept-Encoding
Server: Microsoft-IIS/10.0
Content-Length: 5292
Content-Type: text/javascript; charset=utf-8
Transfer-Encoding: chunked
Content-Encoding: gzip
Expires: Sat, 25 May 2019 13:14:11 GMT
Last-Modified: Fri, 25 May 2018 13:14:11 GMT
Vary: Accept-Encoding
Server: Microsoft-IIS/10.0
Because you are handling the serving of assets via your own code, instead of via the IIS static files module, you have to deal with all response headers yourself.

MVC isn't adding Content-Length for stuff it gzips

We have a controller returning dynamically generated JS code in a JavaScriptResult (ActionResult).
public ActionResult Merchant(int id)
{
var js = "<script>alert('bleh')</script>"; // really retrieved from custom storage
return JavaScript(js); // same as new JavaScriptResult() { Script = js };
}
The default gzip filter is compressing it but not setting Content-Length. Instead, it just sets Transport-Mode: Chunked and leaves it at that.
Arr-Disable-Session-Affinity:True
Cache-Control:public, max-age=600
Content-Encoding:gzip
Content-Type:application/x-javascript; charset=utf-8
Date:Fri, 06 Nov 2015 22:25:17 GMT
Server:Microsoft-IIS/8.0
Timing-Allow-Origin:*
Transfer-Encoding:chunked
Vary:Accept-Encoding
X-AspNet-Version:4.0.30319
X-AspNetMvc-Version:5.2
X-Powered-By:ASP.NET
How can I get it to add the header? I absolutely need this header. Without Content-Length, there's no way to tell if the file completed downloading, e.g. if the connection dropped. Without it, some CDNs like Amazon CloudFront aren't able to properly cache.
After turning off compression, I get a normal Content-Length. It seems trivial for the gzip filter to add this length - there must be an option somewhere?

How implement a OData V4 services like official example services?

The example services mean this:
http://services.odata.org/V4/Northwind/Northwind.svc/
My question is:
Why this service has a ".svc" suffix? As I know, now only two ways can implement odata v4 services on .Net platform, RESTier and WebAPI, see http://odata.github.io/, but both of them not have ".svc". In fact, wcf data services has ".svc", but wcfds not support odata v4.
This example service's response body is high optimization, like this:
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 2015
Content-Type: application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8
Expires: Sat, 24 Oct 2015 05:10:34 GMT
Vary: *
Server: Microsoft-IIS/8.0
X-Content-Type-Options: nosniff
OData-Version: 4.0;
X-AspNet-Version: 4.0.30319
...
{"#odata.context":"http://services.odata.org/V4/Northwind/Northwind.svc/$metadata","value":[{"name":"Categories","kind":"EntitySet","url":"......
just one line, like wcfds again, but my service like this:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; odata.metadata=minimal; charset=utf-8
Expires: -1
Vary: Accept-Encoding
Server: Microsoft-IIS/7.5
OData-Version: 4.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Sat, 24 Oct 2015 06:56:24 GMT
Content-Length: 364
{
"#odata.context":"http://192.168.1.99:908/api/$metadata","value":[
{
"name":"Test","kind":"EntitySet","url":"Test"
},{
"name":"TDefStoreEmp","kind":"EntitySet","url":"TDefStoreEmp"
},{
"name":"TDefEmp","kind":"EntitySet","url":"TDefEmp"
},{
"name":"TDefStore","kind":"EntitySet","url":"TDefStore"
}
]
}
too many lines, how did one line?
So I suspect the example service is based on wcfds, but how it can support V4? in fact, i like wcfds, because it don't need any Controllers, i just want expose a database but don't want open 1433 port on the internet.
My english is not good, please understand and help me, thanks!
You are right, this demo service is implemented using WCF Data Service.
For web api based demo service, you can refer to :
http://services.odata.org/TripPinWebApiService
WCF Data Service for OData V4 is not officially supported, and it is recommended to use WebAPI instead.
This is called indent for JSON, and it was enabled by default.
To disable indent, please add the following to your webapi configuration code:
var formatters = ODataMediaTypeFormatters.Create();
foreach (var formatter in formatters)
{
formatter.MessageWriterSettings.Indent = false;
}
config.Formatters.InsertRange(0, formatters);
The source WCF Data Service is public visible here:
https://github.com/OData/odata.net/tree/WCFDSV4
Please note that the implementation indeed has some gap with OData V4 spec. But if you are interested, you can feel free to build it by yourself it or add new features.
As suggested, it's recommended to use WebAPI OData for setting up OData V4 service. Also, you could choose to use RESTier which resembles wcfds style more.
If you are looking for TripPin OData sample you can find it here:
https://github.com/OData/ODataSamples/tree/master/Scenarios/TripPin

Firefox stored cached incomplete response

I just found a partial response being cached as complete in one of our customer's machines, which rendered the whole website unusable. And I have absolutely no idea, what could possible have gone wrong there.
So what could have possibly gone wrong in the following setup?
On the server-side, we have an ASP.NET-application running. One IHttpHandler handles requests to javascript-files. It basically minifies the files as they are requested and writes the result on the response-stream. It does also log the length of the string being written to the Response-Stream:
String javascript = /* Javascript is retrieved here */;
HttpResponse response = context.Response;
response.ContentEncoding = Encoding.UTF8;
response.ContentType = "application/javascript";
HttpCachePolicy cache = response.Cache;
cache.SetCacheability(HttpCacheability.Public);
cache.SetMaxAge(TimeSpan.FromDays(300));
cache.SetETag(ETag);
cache.SetExpires(DateTime.Now.AddDays(300));
cache.SetLastModified(LastModified);
cache.SetRevalidation(HttpCacheRevalidation.None);
response.Headers.Add("Vary", "Accept-Encoding");
Log.Info("{0} characters sent", javascript.length);
response.Write(javascript);
response.Flush();
response.End();
The content is then normally sent using gzip-encoding with chunked transfer-encoding. Seems simple enough to me.
Unfortunately, I just had a remote-session with a user, where only about 1/3 of the file was in the cache, which broke the file of course (15k instead of 44k). In the cache, the content-encoding was also set to gzip, all communication took place via https.
After having opened the source-file on the user's machine, I just hit Ctrl-F5 and the full content was displayed immediately.
What could have possibly gone wrong?
In case it matters, please find the cache-entry from Firefox below:
Cache entry information
key: <resource-url>
fetch count: 49
last fetched: 2015-04-28 15:31:35
last modified: 2015-04-27 15:29:13
expires: 2016-02-09 14:27:05
Data size: 15998 B
Security: This is a secure document.
security-info: (...)
request-method: GET
request-Accept-Encoding: gzip, deflate
response-head: HTTP/1.1 200 OK
Cache-Control: public, max-age=25920000
Content-Type: application/javascript; charset=utf-8
Content-Encoding: gzip
Expires: Tue, 09 Feb 2016 14:27:12 GMT
Last-Modified: Tue, 02 Jan 2001 11:00:00 GMT
Etag: W/"0"
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
Date: Wed, 15 Apr 2015 13:27:12 GMT
necko:classified: 1
Your clients browser is most likely caching the JavaScript files which would mean the src of your scripts isn't changing.
For instance if you were to request myScripts
<script src="/myScripts.js">
Then the first time, the client would request that file and any further times the browser would read its cache.
You need to append some sort of unique value such as a timestamp to the end of your scripts so even if the browser caches the file, the new timestamp will act like a new file name.
The client receives the new scripts after pressing Ctrl+F5 because this is a shortcut to empty the browsers cache.
MVC has a really nice way of doing this which involves appending a unique code which changes everytime the application or it's app pool is restarted. Check out MVC Bundling and Minification.
Hope this helps!

Rails 4 redirects to 'data:,' in Chrome

There is a weird behavior in Google Chrome, which is also described in this question: rails redirects to 'data:,'
When a new resource is being created and my controller redirects to the show action, chrome initiates loading of a blank page with 'data:,' in the address bar. The reply of the author who asked the above mentioned question is the following:
This is a security feature, the HTML content of the new page matches the HTML content of the submitted form, which Chrome blocks.
However no explanation of how to fix it followed. The behavior is only present in Chrome browser.
I've been googling it and found that editing posts with an iframe in Rails 4.0 causes a redirect to "data:,"
Rails 4 now sets the X-XSS-Protection header for all requests, so the iframe trips up the XSS protection in Chrome after a form submit.
(https://github.com/elektronaut/sugar/issues/41#issuecomment-25987368)
Solution, add it to your controller:
before_filter :disable_xss_protection
protected
def disable_xss_protection
# Disabling this is probably not a good idea,
# but the header causes Chrome to choke when being
# redirected back after a submit and the page contains an iframe.
response.headers['X-XSS-Protection'] = "0"
end
Ok I think I know what this is. You can specify images and text inside a data: protocol, and I believe Chrome is seeing escaped HTML and thinking it is data. Since the mime type is not specified, it leaves the mime type blank after the colon, and just prints the comma.
http://guides.rubyonrails.org/security.html#redirection
Rails 4 automatically escapes HTML, so if you are trying to render HTML, you have to indicate not to escape it. Look at the options for render:
http://guides.rubyonrails.org/security.html#redirection
You can use raw() to render direct HTML.
http://www.webbydude.com/posts/9-the-h-helper-in-rails-3
I'm not convinced it is related to a mimetype issue. I have this issue when a user posts a blog entry that has iframes in its content. When the entry is saved it redirects to the "show" action which will have the user's content (raw/html_safe). Chrome will display the page for a split second and then for some reason re-direct again to the blank "data:," page (in history it will only leave the data:, and the submit page).
here are the response headers i registered:
Ruby 2.0.0 / Rails 4 migrated app with incorrect behavior (staging server)
Cache-Control:max-age=0, no-cache, no-store
Cache-Control:max-age=0, private, must-revalidate
Connection:Keep-Alive
Content-Encoding:gzip
Content-Length:25359
Content-Type:text/html; charset=utf-8
Date:Thu, 23 Jan 2014 16:37:11 GMT
ETag:"6d9d4961ea2df12de67f8a92c43579fb"
Server:Apache
Set-Cookie: _**********_session_dev=1774518c571bf4e65189d607b276e65e; domain=*********.com; path=/; expires=Thu, 23 Jan 2014 18:37:11 -0000; HttpOnly
Status:200 OK
Vary:Accept-Encoding
X-Content-Type-Options:nosniff
X-Frame-Options:SAMEORIGIN
X-Mod-Pagespeed:1.6.29.7-3566
X-Request-Id:9f5314a5-ad01-4aec-bd0f-04e8afd9bdac
X-UA-Compatible:chrome=1
X-XSS-Protection:1; mode=block
Ruby 1.8.7 / Rails 2 app with correct behavior (prod server)
HTTP/1.1 200 OK
Date: Thu, 23 Jan 2014 16:32:53 GMT
Server: Apache
ETag: "f12135ddd373205352f9754328368217"
Cache-Control: private, max-age=0, must-revalidate
Status: 200
X-Mod-Pagespeed: 1.4.26.5-3533
Cache-Control: max-age=0, no-cache, no-store
Vary: Accept-Encoding
Content-Length: 27167
X-Cnection: close
Content-Type: text/html; charset=utf-8
Connection: Keep-Alive
Content-Encoding: gzip
also tried having this as the initial html:
<!DOCTYPE html>
<html>
<head>...
and also just (as random tests to detect what could be wrong)
<!DOCTYPE html>
<head>...
All I know is that if the submitted content has iframes, when redirecting to the blog "display" page chrome's weird behavior kicks in.

Resources