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