QtWebApp
staticfilecontroller.cpp
Go to the documentation of this file.
1
7#include <QFileInfo>
8#include <QDir>
9#include <QDateTime>
10#include <QThread>
11
12using namespace stefanfrings;
13
14StaticFileController::StaticFileController(const QSettings *settings, QObject* parent)
15 :HttpRequestHandler(parent)
16{
17 maxAge=settings->value("maxAge","60000").toInt();
18 encoding=settings->value("encoding","UTF-8").toString();
19 docroot=settings->value("path",".").toString();
20 if(!(docroot.startsWith(":/") || docroot.startsWith("qrc://")))
21 {
22 // Convert relative path to absolute, based on the directory of the config file.
23 #ifdef Q_OS_WIN32
24 if (QDir::isRelativePath(docroot) && settings->format()!=QSettings::NativeFormat)
25 #else
26 if (QDir::isRelativePath(docroot))
27 #endif
28 {
29 QFileInfo configFile(settings->fileName());
30 docroot=QFileInfo(configFile.absolutePath(),docroot).absoluteFilePath();
31 }
32 }
33 qDebug("StaticFileController: docroot=%s, encoding=%s, maxAge=%i",qPrintable(docroot),qPrintable(encoding),maxAge);
34 maxCachedFileSize=settings->value("maxCachedFileSize","65536").toInt();
35 cache.setMaxCost(settings->value("cacheSize","1000000").toInt());
36 cacheTimeout=settings->value("cacheTime","60000").toInt();
37 long int cacheMaxCost=(long int)cache.maxCost();
38 qDebug("StaticFileController: cache timeout=%i, size=%li",cacheTimeout,cacheMaxCost);
39}
40
41
43{
44 QByteArray path=request.getPath();
45 // Check if we have the file in cache
46 qint64 now=QDateTime::currentMSecsSinceEpoch();
47 mutex.lock();
48 CacheEntry* entry=cache.object(path);
49 if (entry && (cacheTimeout==0 || entry->created>now-cacheTimeout))
50 {
51 QByteArray document=entry->document; //copy the cached document, because other threads may destroy the cached entry immediately after mutex unlock.
52 QByteArray filename=entry->filename;
53 mutex.unlock();
54 qDebug("StaticFileController: Cache hit for %s",path.data());
55 setContentType(filename,response);
56 response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000));
57 response.write(document,true);
58 }
59 else
60 {
61 mutex.unlock();
62 // The file is not in cache.
63 qDebug("StaticFileController: Cache miss for %s",path.data());
64 // Forbid access to files outside the docroot directory
65 if (path.contains("/.."))
66 {
67 qWarning("StaticFileController: detected forbidden characters in path %s",path.data());
68 response.setStatus(403,"forbidden");
69 response.write("403 forbidden",true);
70 return;
71 }
72 // If the filename is a directory, append index.html.
73 if (QFileInfo(docroot+path).isDir())
74 {
75 path+="/index.html";
76 }
77 // Try to open the file
78 QFile file(docroot+path);
79 qDebug("StaticFileController: Open file %s",qPrintable(file.fileName()));
80 if (file.open(QIODevice::ReadOnly))
81 {
82 setContentType(path,response);
83 response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000));
84 response.setHeader("Content-Length",QByteArray::number(file.size()));
85 if (file.size()<=maxCachedFileSize)
86 {
87 // Return the file content and store it also in the cache
88 entry=new CacheEntry();
89 while (!file.atEnd() && !file.error())
90 {
91 QByteArray buffer=file.read(65536);
92 response.write(buffer);
93 entry->document.append(buffer);
94 }
95 entry->created=now;
96 entry->filename=path;
97 mutex.lock();
98 cache.insert(request.getPath(),entry,entry->document.size());
99 mutex.unlock();
100 }
101 else
102 {
103 // Return the file content, do not store in cache
104 while (!file.atEnd() && !file.error())
105 {
106 response.write(file.read(65536));
107 }
108 }
109 file.close();
110 }
111 else {
112 if (file.exists())
113 {
114 qWarning("StaticFileController: Cannot open existing file %s for reading",qPrintable(file.fileName()));
115 response.setStatus(403,"forbidden");
116 response.write("403 forbidden",true);
117 }
118 else
119 {
120 response.setStatus(404,"not found");
121 response.write("404 not found",true);
122 }
123 }
124 }
125}
126
127void StaticFileController::setContentType(const QString fileName, HttpResponse &response) const
128{
129 if (fileName.endsWith(".png"))
130 {
131 response.setHeader("Content-Type", "image/png");
132 }
133 else if (fileName.endsWith(".jpg"))
134 {
135 response.setHeader("Content-Type", "image/jpeg");
136 }
137 else if (fileName.endsWith(".gif"))
138 {
139 response.setHeader("Content-Type", "image/gif");
140 }
141 else if (fileName.endsWith(".pdf"))
142 {
143 response.setHeader("Content-Type", "application/pdf");
144 }
145 else if (fileName.endsWith(".txt"))
146 {
147 response.setHeader("Content-Type", qPrintable("text/plain; charset="+encoding));
148 }
149 else if (fileName.endsWith(".html") || fileName.endsWith(".htm"))
150 {
151 response.setHeader("Content-Type", qPrintable("text/html; charset="+encoding));
152 }
153 else if (fileName.endsWith(".css"))
154 {
155 response.setHeader("Content-Type", "text/css");
156 }
157 else if (fileName.endsWith(".js"))
158 {
159 response.setHeader("Content-Type", "text/javascript");
160 }
161 else if (fileName.endsWith(".svg"))
162 {
163 response.setHeader("Content-Type", "image/svg+xml");
164 }
165 else if (fileName.endsWith(".woff"))
166 {
167 response.setHeader("Content-Type", "font/woff");
168 }
169 else if (fileName.endsWith(".woff2"))
170 {
171 response.setHeader("Content-Type", "font/woff2");
172 }
173 else if (fileName.endsWith(".ttf"))
174 {
175 response.setHeader("Content-Type", "application/x-font-ttf");
176 }
177 else if (fileName.endsWith(".eot"))
178 {
179 response.setHeader("Content-Type", "application/vnd.ms-fontobject");
180 }
181 else if (fileName.endsWith(".otf"))
182 {
183 response.setHeader("Content-Type", "application/font-otf");
184 }
185 else if (fileName.endsWith(".json"))
186 {
187 response.setHeader("Content-Type", "application/json");
188 }
189 else if (fileName.endsWith(".xml"))
190 {
191 response.setHeader("Content-Type", "text/xml");
192 }
193 // Todo: add all of your content types
194 else
195 {
196 qDebug("StaticFileController: unknown MIME type for filename '%s'", qPrintable(fileName));
197 }
198}
The request handler generates a response for each HTTP request.
This object represents a single HTTP request.
Definition: httprequest.h:37
QByteArray getPath() const
Get the decoded path of the HTPP request (e.g.
This object represents a HTTP response, used to return something to the web client.
Definition: httpresponse.h:36
void setHeader(const QByteArray name, const QByteArray value)
Set a HTTP response header.
void setStatus(const int statusCode, const QByteArray description=QByteArray())
Set status code and description.
void write(const QByteArray data, const bool lastPart=false)
Write body data to the socket.
void service(HttpRequest &request, HttpResponse &response)
Generates the response.