Skip to content

Commit b12e895

Browse files
authored
Merge pull request #19 from DannyArends/development
Development
2 parents f847c61 + 047570d commit b12e895

File tree

13 files changed

+228
-171
lines changed

13 files changed

+228
-171
lines changed

danode/cgi.d

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ class CGI : Payload {
3838
return -1;
3939
}
4040

41+
@property void notifyovertime() { external.notifyovertime(); }
42+
4143
// Last modified time (not interesting for scripts)
4244
final @property SysTime mtime() { return Clock.currTime(); }
4345

danode/client.d

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,10 @@ class Client : Thread, ClientInterface {
6868
Thread.sleep(dur!"msecs"(2));
6969
}
7070
} catch(Exception e) {
71-
warning("unknown client exception: %s", e.msg);
71+
warning("unknown client exception: %s", e);
7272
stop();
7373
}
74-
custom(1, "CLIENT", "connection %s:%s (%s) closed after %d requests %s (%s msecs)", ip, port, (driver.isSecure() ? "" : ""),
74+
custom(1, "CLIENT", "connection %s:%s (%s) closed after %d requests %s (%s msecs)", ip, port, (driver.isSecure() ? "SSL" : "HTTP"),
7575
driver.requests, driver.senddata, starttime);
7676
driver.destroy(); // Clear the response structure
7777
}

danode/filesystem.d

Lines changed: 34 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -3,142 +3,32 @@ module danode.filesystem;
33
import danode.imports;
44
import danode.statuscode : StatusCode;
55
import danode.mimetypes : mime;
6-
import danode.payload : Payload, PayloadType;
6+
import danode.payload : Payload, FilePayload, PayloadType;
77
import danode.functions : has, isCGI;
88
import danode.log : custom, info, Log, warning, trace, cverbose, NOTSET, NORMAL, DEBUG;
99

10-
class FileInfo : Payload {
11-
public:
12-
bool deflate = false;
13-
private:
14-
string path;
15-
SysTime btime;
16-
bool buffered = false;
17-
char[] buf = null;
18-
char[] encbuf = null;
19-
File* fp = null;
20-
21-
public:
22-
this(string path) { this.path = path; }
23-
24-
// Does the file needs updating
25-
final bool needsupdate(size_t buffersize = 4096) {
26-
if( fitsInBuffer(buffersize) && needsBuffer() ) {
27-
if (!buffered) {
28-
info("need to buffer file record: %s", path);
29-
return true;
30-
}
31-
if (mtime > btime) {
32-
info("re-buffer stale file record: %s", path);
33-
return true;
34-
}
35-
}
36-
return false;
37-
}
38-
39-
// Does the file fit in the buffer
40-
final bool fitsInBuffer(size_t buffersize = 4096) {
41-
if(fileSize() > 0 && fileSize() < buffersize){ return(true); }
42-
return(false);
43-
}
44-
45-
// Buffer the file
46-
final void buffer() { synchronized {
47-
if(buf is null) buf = new char[](fileSize());
48-
buf.length = fileSize();
49-
try {
50-
if(fp is null) fp = new File(path, "rb");
51-
fp.open(path, "rb");
52-
fp.rawRead(buf);
53-
fp.close();
54-
} catch (Exception e) {
55-
warning("exception during buffering '%s': %s", path, e.msg);
56-
return;
57-
}
58-
try {
59-
encbuf = cast(char[])( compress(buf, 9) );
60-
} catch (Exception e) {
61-
warning("exception during compressing '%s': %s", path, e.msg);
62-
}
63-
btime = Clock.currTime();
64-
trace("buffered %s: %d|%d bytes", path, fileSize(), encbuf.length);
65-
buffered = true;
66-
} }
67-
68-
final @property string content(){ return( to!string(bytes(0, length)) ); }
69-
final @property bool realfile() const { return(path.exists()); }
70-
final @property bool hasEncodedVersion() const { return(encbuf !is null); }
71-
final @property bool needsBuffer() { return(!path.isCGI()); }
72-
final @property SysTime mtime() const { if(!realfile){ return btime; } return path.timeLastModified(); }
73-
final @property long ready() { return(true); }
74-
final @property PayloadType type() const { return(PayloadType.Message); }
75-
final @property ptrdiff_t fileSize() const { if(!realfile){ return -1; } return to!ptrdiff_t(path.getSize()); }
76-
final @property long buffersize() const { return cast(long)(buf.length); }
77-
final @property string mimetype() const { return mime(path); }
78-
final @property StatusCode statuscode() const { return StatusCode.Ok; }
79-
80-
final @property ptrdiff_t length() const {
81-
if(hasEncodedVersion && deflate) return(encbuf.length);
82-
return(fileSize());
83-
}
84-
85-
// Send the file as a stream
86-
final char[] asStream(ptrdiff_t from, ptrdiff_t maxsize = 1024) {
87-
if(buf is null) buf = new char[](maxsize);
88-
char[] slice = [];
89-
if (cverbose >= DEBUG && from == 0) write("[STREAM] .");
90-
if (from >= fileSize()) {
91-
trace("from >= filesize, are we still trying to send?");
92-
return([]);
93-
}
94-
try {
95-
if(fp is null) fp = new File(path, "rb");
96-
fp.open(path, "rb");
97-
if(fp.isOpen()) {
98-
fp.seek(from);
99-
slice = fp.rawRead!char(buf);
100-
fp.close();
101-
if (cverbose >= DEBUG) write(".");
102-
if (cverbose >= DEBUG && (from + slice.length) >= fileSize()) write("\n");
103-
}
104-
} catch(Exception e) {
105-
warning("exception %s while streaming file: %s", e.msg, path);
106-
}
107-
return(slice);
108-
}
109-
110-
final char[] bytes(ptrdiff_t from, ptrdiff_t maxsize = 1024){ synchronized {
111-
if (!realfile) { return []; }
112-
trace("file provided is a real file");
113-
if (needsupdate) { buffer(); }
114-
if (!buffered) {
115-
return(asStream(from, maxsize));
116-
} else {
117-
if(hasEncodedVersion && deflate) {
118-
if(from < encbuf.length) return( encbuf[from .. to!ptrdiff_t(min(from+maxsize, $))] );
119-
} else {
120-
if(from < buf.length) return( buf[from .. to!ptrdiff_t(min(from+maxsize, $))] );
121-
}
122-
}
123-
return([]);
124-
} }
125-
}
126-
10+
/* Domain name structure containing files in that domain
11+
Domains are loaded by the FileSystem from the -wwwRoot variable (set to www/ by default)
12+
Note 1: Domains are named as requested by the HTTP client so SSL keynames must match domainnames (e.g.: localhost / 127.0.0.1 / XX.XX.XX.XX or xxx.xx)
13+
Note 2: ./www/localhost existing is required for unit testing */
12714
struct Domain {
128-
FileInfo[string] files;
15+
FilePayload[string] files;
12916
long entries;
13017
long buffered;
13118

13219
@property long buffersize() const { long sum = 0; foreach(ref f; files.byKey){ sum += files[f].buffersize(); } return sum; }
13320
@property long size() const { long sum = 0; foreach(ref f; files.byKey){ sum += files[f].length(); } return sum; }
13421
}
13522

23+
/* File system class that manages the underlying domains
24+
Note: Should this really be thread synchronized access ?
25+
*/
13626
class FileSystem {
13727
private:
138-
string root;
139-
Domain[string] domains;
140-
Log logger;
141-
size_t maxsize;
28+
string root;
29+
Domain[string] domains;
30+
Log logger;
31+
size_t maxsize;
14232

14333
public:
14434
this(Log logger, string root = "./www/", size_t maxsize = 1024 * 512){
@@ -148,24 +38,24 @@ class FileSystem {
14838
scan();
14939
}
15040

151-
// Scan the whole filesystem
41+
/* Scan the whole filesystem for changes */
15242
final void scan(){ synchronized {
15343
foreach (DirEntry d; dirEntries(root, SpanMode.shallow)){ if(d.isDir()){
15444
domains[d.name] = scan(d.name);
15545
} }
15646
} }
15747

158-
// Scan a single directory
48+
/* Scan a single folder */
15949
final Domain scan(string dname){ synchronized {
16050
Domain domain;
16151
foreach (DirEntry f; dirEntries(dname, SpanMode.depth)) {
16252
if (f.isFile()) {
16353
string shortname = replace(f.name[dname.length .. $], "\\", "/");
16454
custom(1, "SCAN", "file: %s -> %s", f.name, shortname);
16555
if (!domain.files.has(shortname)) {
166-
domain.files[shortname] = new FileInfo(f.name);
56+
domain.files[shortname] = new FilePayload(f.name, maxsize);
16757
domain.entries++;
168-
if (domain.files[shortname].needsupdate(maxsize)) {
58+
if (domain.files[shortname].needsupdate()) {
16959
domain.files[shortname].buffer();
17060
domain.buffered++;
17161
}
@@ -177,27 +67,35 @@ class FileSystem {
17767
return(domain);
17868
} }
17969

180-
// Get the localroot of the domain
181-
final string localroot(string hostname) const { return(format("%s%s",this.root, hostname)); }
70+
/* Get the localroot of the domain (TODO: Is there a bug, did I require that this.root should always end in a '/' ?) */
71+
final string localroot(string hostname) const { return(format("%s%s", this.root, hostname)); }
18272

183-
// Get the file at path from the localroot
184-
final FileInfo file(string localroot, string path){ synchronized {
185-
if(!domains[localroot].files.has(path) && exists(format("%s%s", localroot, path))){
186-
custom(1, "SCAN", "new file %s, rescanning index: %s", path, localroot);
73+
/* Get the FilePayload at path from the localroot, with update check on buffers */
74+
final FilePayload file(string localroot, string path){ synchronized {
75+
// New file created after last scan ? -> Scan the whole folder for changes
76+
if (!domains[localroot].files.has(path) && exists(format("%s%s", localroot, path))) {
77+
custom(1, "SCAN", "New file %s, rescanning index: %s", path, localroot);
18778
domains[localroot] = scan(localroot);
18879
}
189-
if(domains[localroot].files.has(path)) return(domains[localroot].files[path]);
190-
return new FileInfo("");
80+
// File exists, buffer the individual file if modified after buffer date
81+
if (domains[localroot].files.has(path)) {
82+
if (domains[localroot].files[path].needsupdate) domains[localroot].files[path].buffer();
83+
return(domains[localroot].files[path]);
84+
}
85+
custom(1, "SCAN", "should not be here not in index, but exists %s, %s", path, localroot);
86+
return new FilePayload("", maxsize);
19187
} }
19288

193-
// Rebuffer all files
89+
/* Rebuffer all file domains from disk,
90+
By reusing domain keys so, we don't buffer new domains. This is ok since we would need to load SSL */
19491
final void rebuffer() {
19592
foreach(ref d; domains.byKey){ foreach(ref f; domains[d].files.byKey){
19693
domains[d].files[f].buffer();
19794
} }
19895
}
19996
}
20097

98+
/* Basic unit-tests should be extended */
20199
unittest {
202100
custom(0, "FILE", "%s", __FILE__);
203101
Log logger = new Log(NORMAL);

danode/imports.d

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ public import std.conv : to;
1212
public import std.datetime : dur, msecs;
1313
public import std.getopt : getopt;
1414
public import std.path : baseName, extension;
15-
public import std.process : pipe, spawnShell, tryWait, wait, kill;
15+
public import std.process : pipe, spawnShell, executeShell, tryWait, wait, kill;
1616
public import std.file : dirEntries, exists, remove, isFile, isDir, timeLastModified, getSize;
1717
public import std.format : format, formatValue;
1818
public import std.regex : regex, match;
1919
public import std.stdio : fgetc, fflush, ftell, stderr, stdin, stdout, writef, writefln, write, writeln;
2020
public import std.string : chomp, endsWith, empty, format, indexOf, join, replace, split, startsWith, strip, toLower, toStringz;
2121
public import std.uuid : md5UUID;
22+
public import std.uri : decodeComponent;
2223
public import std.zlib : compress;
2324

2425
// Public imported structures and enums from core
@@ -29,7 +30,7 @@ public import std.array : Appender;
2930
public import std.datetime : Clock, DateTime, Duration, SysTime;
3031
public import std.format : FormatSpec;
3132
public import std.file : DirEntry, SpanMode;
32-
public import std.process : Config, Pipe;
33+
public import std.process : Pid, Config, Pipe;
3334
public import std.stdio : EOF, File;
3435
public import std.socket : Address, AddressFamily, InternetAddress, ProtocolType, Socket, SocketOption, SocketOptionLevel, SocketSet, SocketShutdown, SocketType;
3536
public import std.traits: SetFunctionAttributes, functionAttributes, EnumMembers;

danode/log.d

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ class Log {
111111
// Log the responses to the request
112112
void logRequest(in ClientInterface cl, in Request rq, in Response rs) {
113113
if (cverbose >= NOTSET) {
114-
string s = format("[%d] %s %s:%s %s%s %s %s", rs.statuscode, htmltime(), cl.ip, cl.port, rq.shorthost, rq.uri, Msecs(rq.starttime), rs.payload.length);
115-
RequestLogFp.writefln(s);
114+
string s = format("[%d] %s %s:%s %s%s %s %s", rs.statuscode, htmltime(), cl.ip, cl.port, rq.shorthost, decodeComponent(rq.uri), Msecs(rq.starttime), rs.payload.length);
115+
RequestLogFp.writeln(s);
116116
custom(-1, "REQ", s);
117117
RequestLogFp.flush();
118118
}

0 commit comments

Comments
 (0)