StaticFileHandler weird behaviour on old browsers
Opened this issue · 9 comments
Hello,
I am developing a web platform that I need to work with iPads running iOS9.
I tried accessing it with Safari, Firefox and Chrome but it seems to me that StaticFileHandler
does not work properly. Indeed, some of my JS and CSS files and my images are not always loaded. I inspected the requests sent by the browser and it seems that some of them randomly never complete, then the browser waits indefinitely for the response.
I did not have any problems with newer versions of iOS. I also tried to load the resources from an Apache server and it works fine.
Do you have any idea why this happens?
Thanks!
Ensure you are passing allowResponseFilters: true
when you make the StaticFileHandler
. If you are using TLS or compression then this needs to be true. I think it does this automatically when TLS is in effect but SFH doesn't know about content compression, so it needs to be set explicitly.
https://github.com/PerfectlySoft/PerfectTemplate/blob/master/Sources/PerfectTemplate/main.swift#L41
allowResponseFilters
is indeed set to true
.
I also encounter this kind of errors:
[Error] Did not parse stylesheet at 'someFile.css' because non CSS MIME types are not allowed in strict mode.
Also, some of my JS files are sometimes incorrectly decoded.
I wonder if this can be related to the cache because it eventually works after I refresh the page multiple times without changing anything in my code.
Update: I dug a little bit deeper using Wireshark and discovered an interesting phenomenon.
I think Perfect might be mixing responses and requests. Indeed, I realised that I sometimes get a JS file as response to a PNG request... any idea why?
Again, it all works fine if I load my resources from an Apache server, which makes be think that the problem indeed comes from Perfect and not the client.
Thanks!
Do you have content compression enabled? If so, remove that filter and see if it has an effect. I'm just trying to eliminate certain causes at this point. If you can, paste in your code for launching the server, just so I can see if there's anything unusual there.
I tried both, with and without compression. It has no effect.
Sure thing, here is the code:
let server = HTTPServer()
let sessionDriver = SessionMemoryDriver()
let routes = [
Route(method: .get, uri: "/**", handler: try! HTTPHandler.staticFiles(data: [
"documentRoot": "./webroot",
"allowResponseFilters": true
])),
Route(method: .get, uri: "/", handler:homeController.indexAction),
// more non-static files routes...
]
server.setRequestFilters([sessionDriver.requestFilter])
server.setResponseFilters([sessionDriver.responseFilter, (Finalizer(), .high)])
/*
With compression:
server.setResponseFilters([sessionDriver.responseFilter, (Finalizer(), .high), (try PerfectHTTPServer.HTTPFilter.contentCompression(data: [:]), HTTPFilterPriority.high)])
*/
server.serverPort = 8181
server.addRoutes(routes)
server.serverName = "localhost"
try! server.start()
Here is my response filter (Finalizer
):
class Finalizer: HTTPResponseFilter {
public func filterHeaders(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) {
if 400...500 ~= response.status.code {
let pageContent = "An error occured"
response.setBody(string: pageContent)
response.setHeader(.contentLength, value: String(pageContent.count))
callback(.done)
}
else {
callback(.continue)
}
}
public func filterBody(response: HTTPResponse, callback: (HTTPResponseFilterResult) -> ()) {
callback(.continue)
}
}
I hope this helps!
Thank you!
I'm at a bit of a loss here. I don't know of anything that would cause what you are experiencing and was unable to reproduce the issue with your code. I banged away on an iOS 9.3.5 iPad and never saw any anomalies with the requests/responses.
Can you check the package versions you are working with? Should be HTTPServer@3.0.23, HTTP@3.3.0, PerfectSession@3.1.5.
Thank you for your reply!
I am indeed using HTTPServer@3.0.23 and PerfectSession@3.1.5. However HTTPServer@3.0.23 is targeting HTTP@3.0.12, hence this is the version I am using instead of 3.3.0.
I managed to design a minimal server needed to reproduce the bug.
Here is my Package.swift
:
// swift-tools-version:4.2
import PackageDescription
let package = Package(
name: "RunnifySwift",
dependencies: [
.package(url: "https://github.com/PerfectlySoft/Perfect-HTTPServer.git", from: "3.0.23"),
.package(url: "https://github.com/PerfectlySoft/Perfect-Mustache.git", from: "3.0.2")
],
targets: [
.target(
name: "RunnifySwift",
dependencies: ["PerfectHTTPServer", "PerfectMustache"]),
.testTarget(
name: "RunnifySwiftTests",
dependencies: ["RunnifySwift"]),
]
)
My server contains 2 Swift files:
main.swift
:
import PerfectHTTP
import PerfectHTTPServer
import PerfectMustache
import Foundation
let server = HTTPServer()
let WEBROOT = "[path-to-webroot]"
func handlePage(_ controller: @escaping (HTTPRequest) throws -> (String, [String: Any])) -> (HTTPRequest, HTTPResponse) -> Void {
return { req, res in
do {
let (view, data) = try controller(req)
mustacheRequest(request: req, response: res, handler: PageHandler(values: data, sessionData: [:]), templatePath: WEBROOT + view)
}
catch(let error) {
res.setBody(string: error.localizedDescription)
res.completed(status: .internalServerError)
}
}
}
func indexAction(request: HTTPRequest) throws -> (String, [String: Any]) {
return ("pages/Index/index.html.mustache", ["listRaces": []])
}
let routes = Routes([
Route(method: .get, uri: "/**", handler: try! HTTPHandler.staticFiles(data: [
"documentRoot": WEBROOT,
"allowResponseFilters": true
])),
Route(method: .get, uri: "/", handler: handlePage(indexAction))
])
server.serverPort = 80
server.addRoutes(routes)
server.serverName = "localhost"
try! server.start()
And PageHandler.swift
:
import Foundation
import PerfectMustache
import PerfectHTTP
struct PageHandler: MustachePageHandler {
let values: MustacheEvaluationContext.MapType
let sessionData: [String: Any]
func extendValuesForResponse(context contxt: MustacheWebEvaluationContext, collector: MustacheEvaluationOutputCollector) {
contxt.extendValues(with: values)
contxt.extendValues(with: sessionData)
do{ try contxt.requestCompleted(withCollector: collector) }
catch {
let response = contxt.webResponse
response.status = .internalServerError
response.setBody(string: "\(error)")
response.completed()
}
}
}
You can find the content of my webroot here (I removed everything that is not necessary).
I am running the server on an Early-2015 MacBook Pro (macOS Mojave 10.14.6) and the bug occurs when I try to access the root of the website from Safari, Firefox and Google Chrome running on both an iPad 2 and iPad 3 running iOS 9.5.3 (latest available version for both models).
Note that the bug does not occur every time. Best way to trigger it is to empty the cache and reload the page. If it does not occur, it is sometimes necessary to repeat the operation 3-4 times.
I hope it helps! 😄
Thank you!
That was very helpful, thank you. I am able to reproduce the problem and will report back when I get to the bottom of what's happening.
So… still not sure exactly what's happening, but I have been able to eliminate several possibilities. It's not mustache; I removed that and just serve the static index page and the problem is still there. It's nothing internal to Perfect mixing up the requests/responses. I added some debugging there and the proper files are being read and returned with the proper mime type and content-length.
The strangest bit though is that if I comment out <script src="/vendors/fontawesome/js/all.js"></script> it all loads fine. I switched it to use all.min.js and it still hangs.