Using Varnish 4, I have a set of backends that're responding with a valid Content-Length header and no Transfer-Encoding header.
On the first hit from a client, rather than responding to the client with those headers, Varnish is dropping the Content-Length header and adding Transfer-Encoding: chunked to the response. (Interestingly, the payload doesn't appear to have any chunks in it - it's one contiguous payload).
This causes serious problems for clients like Flash video players that are trying to do segment-size, bandwidth, etc analysis based on the Content-Length header. Their analysis fails, and they can't do things like multi-bitrate streaming, etc.
I've tried a number of semi-obvious things like:
beresp.do_stream = true
beresp.do_gzip = false
unset req.http.Accept-Encoding
Sample backend response:
HTTP/1.1 200 OK
Cache-Control: public, max-age=600
Content-Type: video/mp4
Date: Tue, 13 May 2014 19:44:35 GMT
Server: Apache
Content-Length: 796618
Connection: keep-alive
Sample varnish response:
HTTP/1.1 200 OK
Server: Apache
Cache-Control: public, max-age=600
Content-Type: video/mp4
Date: Tue, 13 May 2014 23:10:06 GMT
X-Varnish: 2
Age: 0
Transfer-Encoding: chunked
Accept-Ranges: bytes
Subsequent loads of the object do including the Content-Length header, just not the first load into cache.
VCL: https://gist.github.com/onethumb/e64a405cc579909cace1
varnishlog output: https://gist.github.com/onethumb/e66a2bc4727a3a5340b6
Varnish Trac: https://www.varnish-cache.org/trac/ticket/1506
For the time being, do_stream = false will do what you want.
Avoiding chunked encoding for the case where the backend sends unchunked is a possible future improvement to Varnish.
Example:
sub vcl_backend_response {
if(beresp.http.Content-Type ~ "video") {
set beresp.do_stream = false;
set beresp.do_gzip = false;
//set resp.http.Content-Length = beresp.http.Content-Length;
}
if(beresp.http.Edge-Control == "no-store") {
set beresp.uncacheable = true;
set beresp.ttl = 60s;
set beresp.http.Smug-Cacheable = "No";
return(deliver);
}
}
So the solution is not at all intuitive, but you must enable esi processing:
sub vcl_backend_response {
set beresp.do_esi = true;
if(beresp.http.Content-Type ~ "video") {
set beresp.do_stream = true;
set beresp.do_gzip = false;
//set resp.http.Content-Length = beresp.http.Content-Length;
}
if(beresp.http.Edge-Control == "no-store") {
set beresp.uncacheable = true;
set beresp.ttl = 60s;
set beresp.http.Smug-Cacheable = "No";
return(deliver);
}
}
So I discovered this by browsing the source code.
In particular, Varnish does this:
if (!req->disable_esi && req->obj->esidata != NULL) {
/* In ESI mode, we can't know the aggregate length */
req->res_mode &= ~RES_LEN;
req->res_mode |= RES_ESI;
}
The above code sets the res_mode flag.
A little while later:
if (!(req->res_mode & (RES_LEN|RES_CHUNKED|RES_EOF))) {
/* We havn't chosen yet, do so */
if (!req->wantbody) {
/* Nothing */
} else if (req->http->protover >= 11) {
req->res_mode |= RES_CHUNKED;
} else {
req->res_mode |= RES_EOF;
req->doclose = SC_TX_EOF;
}
}
This sets the res_mode flag to RES_CHUNKED if the HTTP protocol is HTTP/1.1 or higher (which it is in your example) and the res_mode flag isn't set. Now even later:
if (req->res_mode & RES_CHUNKED)
http_SetHeader(req->resp, "Transfer-Encoding: chunked");
Varnish sends the chuncked transfer encoding if the RES_CHUNKED flag is set.
The only way I see to effectively disable this is by enabling ESI mode. It gets disabled in a few other ways, but those aren't practical (e.g. for HTTP HEAD requests or pages with a 304 status code).
Upgraded from varnish 4.0 to 5.2 and now this works correctly also for the 1st request.
Related
I'm building a simple server program that needs to return both an image and some text in the response, however, I'm having an issue with Jetty. The text should be included in headers of the HTTP response, but isn't.
Here's the code to return the image:
override fun doPost(request: HttpServletRequest, response: HttpServletResponse) {
response.contentType = "image/png"
response.status = HttpServletResponse.SC_OK
val diff = ImgDiff.getDifference("img1", "img2", tolerance)
//response.writer.println(diff.toString())
ImageIO.write(ImageIO.read(File("diffedFile.png")), "PNG", response.outputStream)
response.addHeader("diff", diff.toString())
}
This works fine, however, the header doesn't contain diff. When I use comment out the ImageIO line and uncomment the one above it, the already commented out one, and change the content type to text/plain diff is included in the headers.
The headers with the image:
Date: Mon, 13 May 2019 22:03:35 GMT
Content-Type: image/png
Transfer-Encoding: chunked
Server: Jetty(9.4.18.v20190429)
The headers without the image (As described in the latter case)
Date: Mon, 13 May 2019 22:10:32 GMT
Content-Type: text/plain;charset=iso-8859-1
diff: 62.62626262626263
Content-Length: 19
Server: Jetty(9.4.18.v20190429)
Am I doing something wrong with Jetty? Can HTTP response images not contain images? I realize I could just return a zip file containing the image and text but I think that's a bit much. Am I ignoring something fundamental to HTTP requests? Please let me know.
It seems to work if I add the headers before I print the image into the stream.
override fun doPost(request: HttpServletRequest, response: HttpServletResponse) {
response.contentType = "image/png"
response.status = HttpServletResponse.SC_OK
val diff = ImgDiff.getDifference("img1", "img2", tolerance)
response.addHeader("diff", diff.toString())
ImageIO.write(ImageIO.read(File("diffedFile.png")), "PNG", response.outputStream)
}
I have an app to create server certificate requests, just as if one were using java keytool or something. I'm trying to return the created certificate request and the key in a zip file, but for the life of me, I can't get my REST controller to respond to the http request. CORRECTION: The controller responds, but the code within the method is never executed.
The server does receive the request, because my CORS filter is executed. But I have a debug set in the controller method, and it's never triggered. Is the signature of the method correct? I need another set of eyes, please?
Here is my controller code:
#RequestMapping(method = RequestMethod.POST, value = "/generateCert/")
public ResponseEntity<InputStreamResource> generateCert(#RequestBody CertInfo certInfo) {
System.out.println("Received request to generate CSR...");
byte[] responseBytes = commonDataService.generateCsr(certInfo);
InputStreamResource resource = new InputStreamResource(new ByteArrayInputStream(responseBytes));
System.out.println("Generated CSR with length of " + responseBytes.length);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=certificate.zip")
.contentType(MediaType.parseMediaType("application/zip"))
.contentLength(responseBytes.length)
.body(resource);
}
And here is the Angular request:
generateCertificate(reqBody: GenerateCert) {
let headers = new Headers();
headers.append('Content-Type', 'application/json');
this.http.post(this.urlGenerateCert, JSON.stringify(reqBody), {headers: headers}).subscribe(
(data) => {
let dataType = data.type;
let binaryData = [];
binaryData.push(data);
this.certBlob = new Blob(binaryData);
});
return this.certBlob;
}
And finally, the request and response headers I copied from the Network Panel:
Response
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type, Authorization, Accept, X-Requested-With, remember-me
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Origin: *
Access-Control-Max-Age: 3600
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Length: 0
Date: Thu, 27 Dec 2018 22:48:00 GMT
Expires: 0
Location: http://localhost:8102/login
Pragma: no-cache
Set-Cookie: JSESSIONID=EDACE17328628D579670AD0FB53A6F35; Path=/; HttpOnly
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Request
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Content-Length: 205
Content-Type: application/json
Host: localhost:8102
Origin: http://localhost:4200
Referer: http://localhost:4200/generateCerts
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36
I really struggled with getting CORS working, so maybe that's interfering with the request? I hate to post all that code unless absolutely necessary. Anybody got any ideas?
Listing of request/response headers lack information on URL, method and most important response status code.
Seeing Location: http://localhost:8102/login among response headers I can guess that it could be 401 Unauthorized or anything else that redirects to the login page. Hence, if there is an auth filter in the filter chain, it may be a culprit.
The following request headers
Host: localhost:8102
Origin: http://localhost:4200
suggests that you are doing CORS and the CORS filter may be involved indeed and fulfill response before the request gets routed to the controller. I suggest setting a breakpoint into the CORS filter (and into others if any) and debug it to the point where the response is returned.
define a proxy.conf.json
{
"/login*": {
"target":"http://localhost:8080",
"secure":false,
"logLevel":"debug"
}
}
now in your package.json
"scripts": {
"start":"ng serve --proxy-config proxy.config.json"
}
I think there is issue while getting connection in both webapp.please try .
When Angular encounters this statement
this.http.post(url,body).subscribe(data => # some code
);
It comes back immediately to run rest of the code while service continues to execute. Just like Future in Java.
Here if you
return this.cert;
You will not get the value that may eventually get populated by the this.http service. Since the page has already rendered and the code executed. You can verify this by including this within and outside the Observable.
console.log(“Inside/outside observable” + new Date().toLocalTimeString());
Thanks to everyone who contributed. I discovered the error was due to the headers of my controller method. After changing them, the method was invoked properly. This is what worked:
#RequestMapping(method = RequestMethod.POST, path = "/generateCert",
produces = {MediaType.APPLICATION_OCTET_STREAM_VALUE}, consumes = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<byte[]> generateCert(#RequestBody CertInfo certInfo) {
byte[] responseBytes = commonDataService.generateCsr(certInfo);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE)
.contentLength(responseBytes.length)
.body(responseBytes);
}
I have multiple tomcat 7.0.56 running on CentOS 6 and a Varnish 4 running on another server Centos. Varnish has to do two important things for us: be a reverse proxy (works like a charm) and compress all data that can be compressed. We don't care about caching in our architecture.
On the second point we have a problem. Varnish gzip CSS and JS and don't gzip html.
In my default.vcl I do not compress files like pictures,swf or my pages designed for mobile and I set beresp.do_gzip true for all other stuff.
My vcl_recv :
sub vcl_recv {
if (req.http.Accept-Encoding) {
if (req.url ~ "\.(jpg|png|gif|gz|tgz|bz2|tbz|mp3|ogg|swf)$" || req.url ~ "Mobile\.") {
unset req.http.Accept-Encoding;
} elsif (req.http.Accept-Encoding ~ "gzip") {
set req.http.Accept-Encoding = "gzip";
} elsif (req.http.Accept-Encoding ~ "deflate" && req.http.user-agent !~ "MSIE") {
set req.http.Accept-Encoding = "deflate";
} else {
# unkown algorithm
unset req.http.Accept-Encoding;
}
}
set req.backend_hint = h.backend(client.identity);
}
My vcl_backend_response:
sub vcl_backend_response {
if (beresp.http.url ~ "\.(jpg|png|gif|gz|tgz|bz2|tbz|mp3|ogg|swf)$" || beresp.http.url ~ "Mobile\.") {
set beresp.do_gzip = false;
}
else {
set beresp.do_gzip = true;
set beresp.http.X-Cache = "ZIP";
}}
All streams passing by Varnish are correctly gzipped except html pages. But these pages have headers almost correct.
Request Headers
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding:gzip, deflate, sdch
Accept-Language:fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4
Cache-Control:no-cache
Connection:keep-alive
Cookie:JSESSIONID=F116C2729E96D2150EEEACEB90F95EA9.node1; UUID=631a2947-14ac4e00ca6-0233de72a654bb34bce4a88d9e172e25
Host:tomcat.domain.tld
Pragma:no-cache
Referer:http://tomcat.domain.tld/path/to/ServletControl?sourceview=liste_menu
User-Agent:Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36
Response Headers
Accept-Ranges:bytes
Age:0
Cache-Control:no-store,no-cache
Connection:keep-alive
Content-Language:fr-FR
Content-Type:text/html;charset=UTF-8
Date:Wed, 07 Jan 2015 14:55:04 GMT
Expires:0
MII:1800
Pragma:no-store,no-cache
Server:Apache-Coyote/1.1
Set-Cookie:UUID=631a2947-...e25; Version=1; Max-Age=10000; Expires=Wed, 07-Jan-2015 17:41:44 GMT; Path=/gce162
Transfer-Encoding:chunked
Vary:Accept-Encoding
Via:1.1 varnish-v4
X-Cache:ZIP
X-Varnish:163870
We can see the tag X-Cache with the value ZIP, the tag Vary with accept-Encoding but no Content-Encoding "gzip".
So I don't understand why varnish don't gzip html and write the tag Vary=accept-Encoding ?
Any help is welcome. Thank you.
Baddou
Believe it's related to Transfer-Encoding:chunked response header be returned due to the content-length not being set for the html files.
To disable the chunked response, try adding set beresp.do_esi = true; in the else loop of the vcl_backend_response.
see also
https://www.varnish-cache.org/trac/ticket/1506 and How do I disable 'Transfer-Encoding: chunked' encoding in Varnish?
I am trying to make modifications to an existing CDN. What I am trying to do is create a short cache time and use conditional GETs to see if the file has been updated.
I am tearing my hair out because even though I am setting a last modified date and seeing it in the response headers, on subsequent get requests I am not seeing an If-Modified-Since header being returned. At first I thought it was my local development environment or the fact that I was using Fiddler as a proxy for testing so I deployed to a QA server. But what I am seeing in Firebug is so different than what I am doing. I see the last modified date, for some reason it is setting my cache-control to private, and I have cleared any header Output Caching and the only header IIS 7.5 is set to write is to enable Http keep-alive, so all the caching should be driven by the code.
This seemed like such a no-brainer, yet I've been adding and removing headers all day with no luck. I checked global.asax and anywhere else (I didn't write the app so I was looking for any hidden surprises and am stumped. Below is the current code and request and response headers. I have the expiration set to 30 seconds just for testing purposes. I have looked at several samples, I don't see myself doing anything different, but it simply won't work.
Response Headersview source
Cache-Control private, max-age=30
Content-Length 597353
Content-Type image/jpg
Date Tue, 03 Sep 2013 21:33:55 GMT
Expires Tue, 03 Sep 2013 21:34:25 GMT
Last-Modified Tue, 03 Sep 2013 21:33:55 GMT
Server Microsoft-IIS/7.5
X-AspNet-Version 4.0.30319
X-AspNetMvc-Version 3.0
X-Powered-By ASP.NET
Request Headersview source
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding gzip, deflate
Accept-Language en-US,en;q=0.5
Connection keep-alive
Cookie __utma=1.759556114.1354835397.1377631052.1377732484.36; __utmz=1.1354835397.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)
Host hqat4app1
User-Agent Mozilla/5.0 (Windows NT 6.1; WOW64; rv:20.0) Gecko/20100101 Firefox/20.0
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Cache.SetLastModified(DateTime.Now);
return new FileContentResult(fileContents, contentType);
The relevant code is:
public ActionResult Resize(int id, int size, bool grayscale)
{
_logger.Debug(() => string.Format("Resize {0} {1} {2}", id, size, grayscale));
string imageFileName = null;
if (id > 0)
using (new UnitOfWorkScope())
imageFileName = RepositoryFactory.CreateReadOnly<Image>().Where(o => o.Id == id).Select(o => o.FileName).SingleOrDefault();
CacheImageSize(id, size);
if (!ImageWasModified(imageFileName))
{
Response.Cache.SetExpires(DateTime.Now.AddSeconds(30));
Response.StatusCode = (int)HttpStatusCode.NotModified;
Response.Status = "304 Not Modified";
return new HttpStatusCodeResult((int)HttpStatusCode.NotModified, "Not-Modified");
}
byte[] fileContents;
if (ShouldReturnDefaultImage(imageFileName))
fileContents = GetDefaultImageContents(size, grayscale);
else
{
bool foundImageFile;
fileContents = GetImageContents(id, size, grayscale, imageFileName, out foundImageFile);
if (!foundImageFile)
{
// No file found, clear cache, disable output cache
//ClearOutputAndRuntimeCacheForImage(id, grayscale);
//Response.DisableKernelCache();
}
}
string contentType = GetBestContentType(imageFileName);
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Cache.SetLastModified(DateTime.Now);
return new FileContentResult(fileContents, contentType);
}
private bool ImageWasModified(string fileName)
{
bool foundImageFile;
string filePath = GetFileOrDefaultPath(fileName, out foundImageFile);
if (foundImageFile)
{
string header = Request.Headers["If-Modified-Since"];
if(!string.IsNullOrEmpty(header))
{
DateTime isModifiedSince;
if (DateTime.TryParse(header, out isModifiedSince))
{
return isModifiedSince < System.IO.File.GetLastWriteTime(filePath);
}
}
}
return true;
}
I've been trying to get this aspx page to serve up a pdf. It works correctly in Firefox, but IE gives
Internet Explorer cannot download getform.aspx from SERVER_NAME
Internet Explorer was not able to open this Internet site. The requested site is either unavailable or cannot be found.
This is the general functionality of my code. It's spread across multiple functions (this is why we're not using WriteFile - sometimes we generate the pdf on the fly), but this is generally it:
FileStream fs = File.Open(Path.Combine(PdfBasePath, "form.pdf"), FileMode.Open, FileAccess.Read);
Stream output = Response.OutputStream;
byte[] buffer = new byte[BUFFER_SIZE];
int read_count = fs.Read(buffer, 0, BUFFER_SIZE);
while (read_count > 0)
{
output.Write(buffer, 0, read_count);
read_count = fs.Read(buffer, 0, BUFFER_SIZE);
}
fs.Close();
Response.Clear();
Response.ContentType = System.Net.Mime.MediaTypeNames.Application.Pdf;
Response.AddHeader("Content-Disposition", "attachment; filename=form.pdf");
Response.Output.Flush();
Response.End();
Looking at Fiddler, the page is being fetched using this:
GET /getform.aspx?Failure=Y&r=someencryptedstring HTTP/1.1
It is being returned to the browser thus:
HTTP/1.1 200 OK
Date: Thu, 09 Apr 2009 22:08:33 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 2.0.50727
Pragma: no-cache
Content-Disposition: attachment; filename=form.pdf
Cache-Control: no-cache, no-store
Pragma: no-cache
Expires: -1
Content-Type: application/pdf
Content-Length: 628548
This is really bugging me. I'm not using SSL, otherwise this KB article would seem to apply. Anyone have any ideas?
Is the Content-Length being returned in the header actually correct for the file you're sending? I'm just comparing this to some production code we use here and it looks like we explicitly set the Content-Length header. If I recall correctly, some browsers have a problem if the header and the actual file size don't match.
Edit
The question author found that changing the Content-Disposition header to application/download instead of application/pdf seems to work around the problem.