mirror of https://github.com/adamdruppe/arsd.git
finally removing obsolete stuff
This commit is contained in:
parent
16b84d2c2d
commit
53e7bbb89d
|
@ -1,6 +0,0 @@
|
||||||
/**
|
|
||||||
|
|
||||||
*/
|
|
||||||
module arsd.domconvenience;
|
|
||||||
|
|
||||||
// the contents of this file are back in dom.d for now. I might split them out later.
|
|
311
obsolete/httpd.d
311
obsolete/httpd.d
|
@ -1,311 +0,0 @@
|
||||||
// This thing sucks. It's primarily to prove that cgi.d *can* work
|
|
||||||
// with an embedded http server without much difficulty, so you aren't
|
|
||||||
// tied to CGI if you don't want to be, even if you have a huge codebase
|
|
||||||
// built on cgi.d already.
|
|
||||||
|
|
||||||
// But this particular module is no where near ready for serious use.
|
|
||||||
// (it does do reasonably well under controlled conditions though)
|
|
||||||
|
|
||||||
module arsd.httpd;
|
|
||||||
|
|
||||||
public import arsd.cgi;
|
|
||||||
|
|
||||||
import arsd.netman;
|
|
||||||
|
|
||||||
import std.range;
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
import arsd.curl;
|
|
||||||
void handler(Cgi cgi) {
|
|
||||||
cgi.write("hello world!");
|
|
||||||
cgi.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
serveHttp(&handler, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
void serveHttp(CustomCgi)(void function(Cgi) requestHandler, ushort port) if(is(CustomCgi: Cgi)) {
|
|
||||||
auto netman = new NetMan!(CustomCgi)(requestHandler);
|
|
||||||
netman.listen(port);
|
|
||||||
for(;;)
|
|
||||||
try
|
|
||||||
netman.proceed();
|
|
||||||
catch(ConnectionException e)
|
|
||||||
e.c.disconnectNow();
|
|
||||||
catch(Exception e)
|
|
||||||
writefln("Exception: %s", e.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
class NetMan(CustomCgi) : NetworkManager /*if(is(CustomCgi : Cgi))*/ {
|
|
||||||
void function(Cgi) requestHandler;
|
|
||||||
|
|
||||||
this(void function(Cgi) requestHandler) {
|
|
||||||
this.requestHandler = requestHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
override Connection allocConnection(int port) {
|
|
||||||
return new HttpdConnection!CustomCgi(requestHandler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HttpdConnection(CustomCgi) : Connection /* if(is(CustomCgi : Cgi)) */ {
|
|
||||||
// The way this rolls is to get the whole thing in memory, then pass it off to Cgi to do the rest
|
|
||||||
|
|
||||||
this(void function(Cgi) requestHandler) {
|
|
||||||
handler = requestHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
void function(Cgi) handler;
|
|
||||||
|
|
||||||
int state;
|
|
||||||
string[] headers;
|
|
||||||
immutable(ubyte)[] data;
|
|
||||||
int contentLength;
|
|
||||||
bool chunked;
|
|
||||||
int chunkSize = 0;
|
|
||||||
string separator = "\r\n";
|
|
||||||
bool closeConnection;
|
|
||||||
|
|
||||||
void log(in ubyte[] a) {
|
|
||||||
data ~= a;
|
|
||||||
}
|
|
||||||
|
|
||||||
void finishRequest() {
|
|
||||||
state = 0;
|
|
||||||
separator = "\r\n";
|
|
||||||
|
|
||||||
// writeln("FINISHED");
|
|
||||||
|
|
||||||
scope(exit) {
|
|
||||||
if(closeConnection)
|
|
||||||
disconnect();
|
|
||||||
closeConnection = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Cgi cgi;
|
|
||||||
|
|
||||||
try {
|
|
||||||
cgi = new CustomCgi(headers, data, peerAddress(),
|
|
||||||
cast(void delegate(const(ubyte)[])) &this.write, 0, &this.flush);
|
|
||||||
} catch(Throwable t) {
|
|
||||||
write("HTTP/1.1 400 Bad Request\r\n");
|
|
||||||
write("Content-Type: text/plain\r\n");
|
|
||||||
string s = t.toString();
|
|
||||||
write("Content-Length: "~to!string(s.length)~"\r\n");
|
|
||||||
write("Connection: close\r\n");
|
|
||||||
write("\r\n");
|
|
||||||
write(s);
|
|
||||||
|
|
||||||
closeConnection = true;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
scope(exit) {
|
|
||||||
cgi.close();
|
|
||||||
cgi.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
handler(cgi);
|
|
||||||
} catch(Throwable e) {
|
|
||||||
cgi.setResponseStatus("500 Internal Server Error");
|
|
||||||
cgi.write(e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override void onDataReceived(){
|
|
||||||
auto a = read();
|
|
||||||
|
|
||||||
// writeln("data received ", state, "\n", cast(string) a);
|
|
||||||
|
|
||||||
more:
|
|
||||||
switch(state) {
|
|
||||||
default: assert(0);
|
|
||||||
case 0: // reading the headers
|
|
||||||
// while it's supposed to be \r\n, we want
|
|
||||||
// to be permissive here to avoid hanging if
|
|
||||||
// the wrong thing comes.
|
|
||||||
try_again:
|
|
||||||
int l = locationOf(a, separator ~ separator);
|
|
||||||
if(l == -1) {
|
|
||||||
if(separator.length > 1) {
|
|
||||||
separator = "\n";
|
|
||||||
goto try_again;
|
|
||||||
} else {
|
|
||||||
separator = "\r\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
return; // not enough data
|
|
||||||
}
|
|
||||||
changeReadPosition(l+separator.length * 2); // we're now at the beginning of the data
|
|
||||||
|
|
||||||
data.length = 0;
|
|
||||||
contentLength = 0;
|
|
||||||
|
|
||||||
string hdrs = cast(string) a[0..l].idup;
|
|
||||||
a = read(); // advance ourselves
|
|
||||||
headers = hdrs.split(separator);
|
|
||||||
|
|
||||||
chunked = false;
|
|
||||||
|
|
||||||
if(headers.length == 0) {
|
|
||||||
disconnect();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(headers[0].indexOf("HTTP/1.0") != -1)
|
|
||||||
closeConnection = true; // always one request per connection with 1.0
|
|
||||||
|
|
||||||
foreach(ref h; headers[1..$]) {
|
|
||||||
int colon = h.indexOf(":");
|
|
||||||
if(colon == -1)
|
|
||||||
throw new Exception("Http headers need colons");
|
|
||||||
string name = h[0..colon].tolower;
|
|
||||||
string value = h[colon+2..$]; // FIXME?
|
|
||||||
|
|
||||||
switch(name) {
|
|
||||||
case "transfer-encoding":
|
|
||||||
if(value == "chunked")
|
|
||||||
chunked = true;
|
|
||||||
break;
|
|
||||||
case "content-length":
|
|
||||||
contentLength = to!int(value);
|
|
||||||
break;
|
|
||||||
case "connection":
|
|
||||||
if(value == "close")
|
|
||||||
closeConnection = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// leave it unmolested for passthrough
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// forward the header and advance our state
|
|
||||||
state = 1;
|
|
||||||
// break; // fall through to read some more data if we have any
|
|
||||||
case 1: // reading Content-Length type data
|
|
||||||
// We need to read up the data we have, and write it out as a chunk.
|
|
||||||
if(!chunked) {
|
|
||||||
if(a.length <= contentLength) {
|
|
||||||
log(a);
|
|
||||||
contentLength -= a.length;
|
|
||||||
resetRead();
|
|
||||||
// we just finished it off, terminate the chunks
|
|
||||||
if(contentLength == 0) {
|
|
||||||
finishRequest();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// we actually have *more* here than we need....
|
|
||||||
log(a[0..contentLength]);
|
|
||||||
contentLength = 0;
|
|
||||||
finishRequest();
|
|
||||||
|
|
||||||
changeReadPosition(contentLength);
|
|
||||||
a = read();
|
|
||||||
// we're done
|
|
||||||
goto more; // see if we can make use of the rest of the data
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// decode it, modify it, then reencode it
|
|
||||||
// If here, we are at the beginning of a chunk.
|
|
||||||
int loc = locationOf(a, "\r\n");
|
|
||||||
if(loc == -1) {
|
|
||||||
return; // don't have the length
|
|
||||||
}
|
|
||||||
|
|
||||||
string hex;
|
|
||||||
hex = "";
|
|
||||||
for(int i = 0; i < loc; i++) {
|
|
||||||
char c = a[i];
|
|
||||||
if(c >= 'A' && c <= 'Z')
|
|
||||||
c += 0x20;
|
|
||||||
if((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z')) {
|
|
||||||
hex ~= c;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(hex.length);
|
|
||||||
|
|
||||||
int power = 1;
|
|
||||||
int size = 0;
|
|
||||||
foreach(cc1; retro(hex)) {
|
|
||||||
dchar cc = cc1;
|
|
||||||
if(cc >= 'a' && cc <= 'z')
|
|
||||||
cc -= 0x20;
|
|
||||||
int val = 0;
|
|
||||||
if(cc >= '0' && cc <= '9')
|
|
||||||
val = cc - '0';
|
|
||||||
else
|
|
||||||
val = cc - 'A' + 10;
|
|
||||||
|
|
||||||
size += power * val;
|
|
||||||
power *= 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
chunkSize = size;
|
|
||||||
assert(size >= 0);
|
|
||||||
|
|
||||||
if(loc + 2 > a.length) {
|
|
||||||
return; // need more data
|
|
||||||
}
|
|
||||||
changeReadPosition(loc+2); // skips the chunk header
|
|
||||||
a = read();
|
|
||||||
|
|
||||||
if(chunkSize == 0) { // we're done with the response
|
|
||||||
state = 3;
|
|
||||||
goto more;
|
|
||||||
} else {
|
|
||||||
state = 2;
|
|
||||||
goto more;
|
|
||||||
}
|
|
||||||
|
|
||||||
resetRead();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 2: // reading a chunk
|
|
||||||
// if we got here, will change must be true....
|
|
||||||
if(a.length < chunkSize + 2) {
|
|
||||||
return; // we want to handle the whole chunk at once
|
|
||||||
}
|
|
||||||
|
|
||||||
log(a[0..chunkSize]);
|
|
||||||
|
|
||||||
state = 1;
|
|
||||||
|
|
||||||
if(a.length > chunkSize + 2) {
|
|
||||||
assert(a[chunkSize] == 13);
|
|
||||||
assert(a[chunkSize+1] == 10);
|
|
||||||
changeReadPosition(chunkSize + 2); // skip the \r\n
|
|
||||||
a = read();
|
|
||||||
chunkSize = 0;
|
|
||||||
state = 1;
|
|
||||||
goto more;
|
|
||||||
} else {
|
|
||||||
chunkSize = 0;
|
|
||||||
resetRead();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 3: // reading footers
|
|
||||||
// if we got here, will change must be true....
|
|
||||||
int loc = locationOf(a, "\r\n");
|
|
||||||
if(loc == -1) {
|
|
||||||
return; // not done yet
|
|
||||||
} else {
|
|
||||||
assert(loc == 0);
|
|
||||||
changeReadPosition(loc+2); // FIXME: should handle footers properly
|
|
||||||
finishRequest();
|
|
||||||
a = read();
|
|
||||||
|
|
||||||
goto more;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
module arsd.image;
|
|
||||||
|
|
||||||
/**
|
|
||||||
This provides two image classes and a bunch of functions that work on them.
|
|
||||||
|
|
||||||
Why are they separate classes? I think the operations on the two of them
|
|
||||||
are necessarily different. There's a whole bunch of operations that only
|
|
||||||
really work on truecolor (blurs, gradients), and a few that only work
|
|
||||||
on indexed images (palette swaps).
|
|
||||||
|
|
||||||
Even putpixel is pretty different. On indexed, it is a palette entry's
|
|
||||||
index number. On truecolor, it is the actual color.
|
|
||||||
|
|
||||||
A greyscale image is the weird thing in the middle. It is truecolor, but
|
|
||||||
fits in the same size as indexed. Still, I'd say it is a specialization
|
|
||||||
of truecolor.
|
|
||||||
|
|
||||||
There is a subset that works on both
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// the basic image definitions have all been moved to the color module
|
|
||||||
public import arsd.color;
|
|
|
@ -1,3 +0,0 @@
|
||||||
/* this file is now obsolete, use png.d instead */
|
|
||||||
module arsd.lazypng;
|
|
||||||
static assert(0, "This file is now obsolete. Use png.d instead. The only difference on the user side is the old lazypng.writePng is now png.writePngLazy");
|
|
|
@ -1,540 +0,0 @@
|
||||||
// This is a little core I wrote a few years ago to write custom tcp servers
|
|
||||||
// in D. It only works on Linux, doesn't care much for efficiency, and uses
|
|
||||||
// select() to handle multiple connections in a single thread.
|
|
||||||
|
|
||||||
// It is my hope that someday soon, Phobos will get a network module that
|
|
||||||
// can completely replace this. But, until that happens, this little thing
|
|
||||||
// is better than nothing.
|
|
||||||
|
|
||||||
// See httpd.d for a (pretty bad) http server built on this, or rtud.d
|
|
||||||
// for a real time update helper program that also uses it.
|
|
||||||
|
|
||||||
// If anyone is interested, I also have chat (including TOC protocol to be
|
|
||||||
// a little AIM client) and minimal gui (version one of my D windowing system
|
|
||||||
// idea... which is still on my todo list, but it's going to be aeons before
|
|
||||||
// it's remotely usable at the rate I'm going) built on this module.
|
|
||||||
|
|
||||||
module arsd.netman;
|
|
||||||
|
|
||||||
version(linux):
|
|
||||||
|
|
||||||
import std.c.linux.linux;
|
|
||||||
import std.c.linux.socket;
|
|
||||||
|
|
||||||
static import std.string;
|
|
||||||
static import std.c.string;
|
|
||||||
static import std.stdio;
|
|
||||||
static import std.conv;
|
|
||||||
|
|
||||||
alias std.c.linux.socket sock;
|
|
||||||
alias std.c.linux.linux linux;
|
|
||||||
|
|
||||||
enum int PF_INET = 2;
|
|
||||||
enum int AF_INET = PF_INET;
|
|
||||||
|
|
||||||
|
|
||||||
int max(int a, int b){ return a > b ? a : b; }
|
|
||||||
|
|
||||||
class ConnectionException : Exception {
|
|
||||||
this(string message, Connection conn) {
|
|
||||||
msg = message;
|
|
||||||
c = conn;
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
string msg;
|
|
||||||
public:
|
|
||||||
Connection c;
|
|
||||||
override string toString() {
|
|
||||||
return msg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class NetworkManager {
|
|
||||||
// you might want to override this to construct subclasses of connection
|
|
||||||
protected:
|
|
||||||
Connection allocConnection(int port) {
|
|
||||||
if(auto a = port in allocs)
|
|
||||||
return (*a)();
|
|
||||||
assert(0);
|
|
||||||
}
|
|
||||||
Connection allocOutgoingConnection(int port){
|
|
||||||
if(auto a = port in allocs)
|
|
||||||
return (*a)();
|
|
||||||
return new Connection();
|
|
||||||
assert(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public:
|
|
||||||
// void openSerialPort();
|
|
||||||
this() {
|
|
||||||
if(!handledSignal) {
|
|
||||||
handledSignal = true;
|
|
||||||
signal(SIGPIPE, SIG_IGN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
static bool handledSignal = false;
|
|
||||||
|
|
||||||
void setConnectionSpawner(int port, Connection delegate() c) {
|
|
||||||
assert(c !is null);
|
|
||||||
allocs[port] = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setConnectionSpawner(int port, Connection function() c) {
|
|
||||||
setConnectionSpawner(port, { return c(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
size_t numActiveConnections(){
|
|
||||||
return connections.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
Connection connect(string ipaddr, ushort port){
|
|
||||||
Connection c = allocOutgoingConnection(port);
|
|
||||||
|
|
||||||
c.parentManager = this;
|
|
||||||
|
|
||||||
hostent* h;
|
|
||||||
sockaddr_in addr;
|
|
||||||
|
|
||||||
h = gethostbyname(std.string.toStringz(ipaddr));
|
|
||||||
if(h is null)
|
|
||||||
throw new Exception("gethostbyname");
|
|
||||||
|
|
||||||
int s = socket(PF_INET, SOCK_STREAM, 0);
|
|
||||||
if(s == -1)
|
|
||||||
throw new Exception("socket");
|
|
||||||
|
|
||||||
scope(failure)
|
|
||||||
close(s);
|
|
||||||
|
|
||||||
addr.sin_family = AF_INET;
|
|
||||||
addr.sin_port = htons(port);
|
|
||||||
std.c.string.memcpy(&addr.sin_addr.s_addr, h.h_addr, h.h_length);
|
|
||||||
|
|
||||||
if(sock.connect(s, cast(sockaddr*) &addr, addr.sizeof) == -1)
|
|
||||||
throw new Exception("connect");
|
|
||||||
|
|
||||||
addConnection(c, s, cast(sockaddr) addr, port);
|
|
||||||
c.onConnect();
|
|
||||||
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
void openStdin() {
|
|
||||||
Connection c = allocConnection(0);
|
|
||||||
|
|
||||||
c.parentManager = this;
|
|
||||||
|
|
||||||
sockaddr addr;
|
|
||||||
|
|
||||||
addConnection(c, 0, addr, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void listen(ushort port, int queue = 4){
|
|
||||||
int s = socket(PF_INET, SOCK_STREAM, 0);
|
|
||||||
if(s == -1)
|
|
||||||
throw new Exception("socket");
|
|
||||||
scope(failure)
|
|
||||||
close(s);
|
|
||||||
|
|
||||||
sockaddr_in addr;
|
|
||||||
addr.sin_family = AF_INET;
|
|
||||||
addr.sin_port = htons(port);
|
|
||||||
addr.sin_addr.s_addr = INADDR_ANY;
|
|
||||||
|
|
||||||
// HACKISH
|
|
||||||
int on = 1;
|
|
||||||
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, on.sizeof);
|
|
||||||
// end hack
|
|
||||||
|
|
||||||
|
|
||||||
if(bind(s, cast(sockaddr*) &addr, addr.sizeof) == -1)
|
|
||||||
throw new Exception("bind");
|
|
||||||
|
|
||||||
if(sock.listen(s, queue) == -1)
|
|
||||||
throw new Exception("listen");
|
|
||||||
|
|
||||||
listenings.length = listenings.length + 1;
|
|
||||||
|
|
||||||
listenings[$-1].s = s;
|
|
||||||
listenings[$-1].port = port;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// returns true if stuff happened, false if timed out
|
|
||||||
/// timeout of 0 means wait forever - set it to -1 if you want it to return immediately
|
|
||||||
bool proceed(int timeout = 0){
|
|
||||||
//events to handle: data ready to read, timeout, new connection, connection error
|
|
||||||
// stuff to do: write data
|
|
||||||
|
|
||||||
try_again:
|
|
||||||
|
|
||||||
fd_set rdfs;
|
|
||||||
fd_set writefs;
|
|
||||||
timeval tv;
|
|
||||||
|
|
||||||
FD_ZERO(&rdfs);
|
|
||||||
FD_ZERO(&writefs);
|
|
||||||
|
|
||||||
int biggest = -1;
|
|
||||||
|
|
||||||
foreach(connection; connections){
|
|
||||||
if(connection.socket == -1)
|
|
||||||
continue;
|
|
||||||
if(connection.writeBufferLength > 0)
|
|
||||||
FD_SET(connection.socket, &writefs);
|
|
||||||
|
|
||||||
FD_SET(connection.socket, &rdfs);
|
|
||||||
biggest = max(biggest, connection.socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach(c; listenings){
|
|
||||||
FD_SET(c.s, &rdfs);
|
|
||||||
biggest = max(biggest, c.s);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if(timeout == -1)
|
|
||||||
tv.tv_sec = 0;
|
|
||||||
else
|
|
||||||
tv.tv_sec = timeout;
|
|
||||||
tv.tv_usec = 0;
|
|
||||||
|
|
||||||
int ret;
|
|
||||||
if(timeout == 0)
|
|
||||||
ret = linux.select(biggest + 1, &rdfs, &writefs, null, null);
|
|
||||||
else
|
|
||||||
ret = linux.select(biggest + 1, &rdfs, &writefs, null, &tv);
|
|
||||||
|
|
||||||
if(ret == -1) {
|
|
||||||
import core.stdc.errno;
|
|
||||||
import std.conv;
|
|
||||||
if(errno == 4) // interrupted by signal
|
|
||||||
goto try_again;
|
|
||||||
else
|
|
||||||
throw new Exception("select " ~ to!string(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(ret) {
|
|
||||||
// data ready somewhere
|
|
||||||
|
|
||||||
|
|
||||||
foreach(connection; connections){
|
|
||||||
if(connection.socket == -1)
|
|
||||||
continue;
|
|
||||||
if(connection.writeBufferLength > 0)
|
|
||||||
if(FD_ISSET(connection.socket, &writefs)){
|
|
||||||
auto b = connection.writeBuffer[connection.writeBufferPosition..(connection.writeBufferPosition+connection.writeBufferLength)];
|
|
||||||
//auto num = send(connection.socket, b.ptr, b.length, 0);
|
|
||||||
auto num = write(connection.socket, b.ptr, b.length);
|
|
||||||
if(num < 0)
|
|
||||||
throw new ConnectionException("send", connection);
|
|
||||||
|
|
||||||
connection.writeBufferLength -= num;
|
|
||||||
if(connection.writeBufferLength > 0)
|
|
||||||
connection.writeBufferPosition += num;
|
|
||||||
else
|
|
||||||
connection.writeBufferPosition = 0;
|
|
||||||
|
|
||||||
connection.timeOfLastActivity = now;
|
|
||||||
}
|
|
||||||
if(FD_ISSET(connection.socket, &rdfs)){
|
|
||||||
size_t s = connection.readBufferPosition + connection.readBufferLength;
|
|
||||||
s += 1024;
|
|
||||||
if(connection.readBuffer.length < s)
|
|
||||||
connection.readBuffer.length = s;
|
|
||||||
//auto size = recv(connection.socket, connection.readBuffer.ptr + connection.readBufferPosition + connection.readBufferLength, 1024, 0);
|
|
||||||
//std.stdio.writefln("read buffer length: %s", connection.readBufferLength);
|
|
||||||
auto size = read(connection.socket, connection.readBuffer.ptr + connection.readBufferPosition + connection.readBufferLength, 1024);
|
|
||||||
if(size == 0){
|
|
||||||
connection.disconnectQueued = true;
|
|
||||||
connection.reason = "size == 0";
|
|
||||||
}
|
|
||||||
else if (size < 0)
|
|
||||||
throw new ConnectionException("recv", connection);
|
|
||||||
else {
|
|
||||||
connection.readBufferLength += size;
|
|
||||||
connection.onDataReceived();
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.timeOfLastActivity = now;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach(c; listenings){
|
|
||||||
if(FD_ISSET(c.s, &rdfs)){
|
|
||||||
uint i;
|
|
||||||
sockaddr addr;
|
|
||||||
i = addr.sizeof;
|
|
||||||
int s = accept(c.s, &addr, &i);
|
|
||||||
|
|
||||||
if(s == -1)
|
|
||||||
throw new Exception("accept");
|
|
||||||
|
|
||||||
version(threaded_connections) {
|
|
||||||
auto con = allocConnection(c.port);
|
|
||||||
con.socket = s;
|
|
||||||
con.addr = addr;
|
|
||||||
con.port = c.port;
|
|
||||||
auto t = new ConnectionThread(con);
|
|
||||||
t.start();
|
|
||||||
} else {
|
|
||||||
auto con = allocConnection(c.port);
|
|
||||||
con.parentManager = this;
|
|
||||||
addConnection(con, s, addr, c.port);
|
|
||||||
con.onRemoteConnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// One last run through the connections to remove any stale ones...
|
|
||||||
for(int a = 0; a < connections.length; a++) {
|
|
||||||
if( /* HACK */ connections[a].socket != 0 && /* END HACK */
|
|
||||||
connectionTimeOut && (now-connections[a].timeOfLastActivity) > connectionTimeOut){
|
|
||||||
connections[a].disconnectQueued = true;
|
|
||||||
connections[a].reason = "stale";
|
|
||||||
}
|
|
||||||
if(connections[a].disconnectQueued && connections[a].writeBufferLength == 0)
|
|
||||||
connections[a].disconnectNow();
|
|
||||||
|
|
||||||
if(connections[a].socket == -1){
|
|
||||||
for(int b = a; b < connections.length-1; b++)
|
|
||||||
connections[b] = connections[b+1];
|
|
||||||
connections.length = connections.length - 1;
|
|
||||||
a--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// timed out
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// FIXME: for some reason, this whole timeout thing doesn't actually work correctly.
|
|
||||||
|
|
||||||
int connectionTimeOut = 0;//152;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Connection delegate()[int] allocs;
|
|
||||||
|
|
||||||
void addConnection(Connection c, int s, sockaddr addr, int port){
|
|
||||||
c.socket = s;
|
|
||||||
c.addr = addr;
|
|
||||||
c.port = port;
|
|
||||||
|
|
||||||
if(c.socket < 0)
|
|
||||||
throw new Exception("don't add bad sockets");
|
|
||||||
connections.length = connections.length + 1;
|
|
||||||
connections[$-1] = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ListeningSocket{
|
|
||||||
int s;
|
|
||||||
int port;
|
|
||||||
}
|
|
||||||
|
|
||||||
ListeningSocket[] listenings;
|
|
||||||
Connection[] connections;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// You should only ever construct these in the allocConnection delegate for the manager.
|
|
||||||
// Otherwise it will probably crash/
|
|
||||||
class Connection {
|
|
||||||
public:
|
|
||||||
|
|
||||||
NetworkManager parentManager;
|
|
||||||
|
|
||||||
protected this(){ timeOfLastActivity = now; }
|
|
||||||
|
|
||||||
string reason;
|
|
||||||
|
|
||||||
~this(){
|
|
||||||
disconnectNow(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void disconnectNow(bool cod = true){
|
|
||||||
if(socket >= 0){
|
|
||||||
close(socket);
|
|
||||||
socket = -1;
|
|
||||||
if(cod)
|
|
||||||
onDisconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void disconnect() {
|
|
||||||
disconnectQueued = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writes the pending data to the socket now instead of waiting for the manager to proceed
|
|
||||||
void flush(){
|
|
||||||
if(writeBufferLength > 0) {
|
|
||||||
auto b = writeBuffer[writeBufferPosition..(writeBufferPosition+writeBufferLength)];
|
|
||||||
auto num = .write(socket, b.ptr, b.length);
|
|
||||||
if(num < 0)
|
|
||||||
throw new ConnectionException("send", this);
|
|
||||||
|
|
||||||
writeBufferLength -= num;
|
|
||||||
if(writeBufferLength > 0)
|
|
||||||
writeBufferPosition += num;
|
|
||||||
else
|
|
||||||
writeBufferPosition = 0;
|
|
||||||
|
|
||||||
timeOfLastActivity = now;
|
|
||||||
fsync(socket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// reads the requested amount now, blocking until you get it all.
|
|
||||||
void fetch(int amount){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void write(const(void)[] data){
|
|
||||||
if(socket < 0)
|
|
||||||
throw new ConnectionException("cannot write to a closed connection", this);
|
|
||||||
|
|
||||||
size_t newEnd = writeBufferPosition + writeBufferLength + data.length;
|
|
||||||
|
|
||||||
if(newEnd >= writeBuffer.length)
|
|
||||||
writeBuffer.length = newEnd;
|
|
||||||
|
|
||||||
writeBuffer[writeBufferLength..newEnd] = cast(const(ubyte)[]) data[0..$];
|
|
||||||
|
|
||||||
writeBufferLength += data.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const(ubyte)[] read(){
|
|
||||||
if(socket < 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
auto d = readBuffer[readBufferPosition..(readBufferPosition + readBufferLength)];
|
|
||||||
rofl = readBufferPosition;
|
|
||||||
copter = readBufferLength;
|
|
||||||
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
private size_t rofl, copter;
|
|
||||||
|
|
||||||
void changeReadPosition(int p) {
|
|
||||||
//assert(p <= copter);
|
|
||||||
//std.stdio.writefln("%d from %d of %d", p, readBufferPosition, readBufferLength);
|
|
||||||
readBufferLength = copter - p;
|
|
||||||
readBufferPosition = rofl + p;
|
|
||||||
//std.stdio.writefln("became %d of %d", readBufferPosition, readBufferLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
void resetRead(){
|
|
||||||
readBufferLength = 0;
|
|
||||||
readBufferPosition = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// These events should be reimplemented by subclasses
|
|
||||||
|
|
||||||
void onDataReceived(){
|
|
||||||
// read() the data and process it
|
|
||||||
// then resetRead() to prepare for the next batch
|
|
||||||
resetRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
// we just connected to someone
|
|
||||||
void onConnect(){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// someone just connected to us
|
|
||||||
void onRemoteConnect(){
|
|
||||||
onConnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
void onDisconnect(){
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ready() {
|
|
||||||
return socket != -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
string peerAddress() {
|
|
||||||
if(addr.sa_family != 2)
|
|
||||||
throw new ConnectionException("peerAddress not supported for this connection", this);
|
|
||||||
|
|
||||||
return std.conv.to!string(cast(const(ubyte)[]) addr.sa_data[2..6],
|
|
||||||
"", ".", ""
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
ushort peerPort() {
|
|
||||||
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
int fd() { return socket; }
|
|
||||||
private:
|
|
||||||
int socket = -1;
|
|
||||||
|
|
||||||
ubyte[] writeBuffer;
|
|
||||||
ubyte[] readBuffer;
|
|
||||||
|
|
||||||
size_t writeBufferPosition;
|
|
||||||
size_t writeBufferLength;
|
|
||||||
|
|
||||||
size_t readBufferPosition;
|
|
||||||
size_t readBufferLength;
|
|
||||||
|
|
||||||
bool disconnectQueued;
|
|
||||||
|
|
||||||
sockaddr addr;
|
|
||||||
int port;
|
|
||||||
|
|
||||||
int timeOfLastActivity;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
import std.date;
|
|
||||||
int now() {
|
|
||||||
return cast(int) getUTCtime();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
version(threaded_connections) {
|
|
||||||
import core.thread;
|
|
||||||
import std.stdio : writeln;
|
|
||||||
class ConnectionThread : Thread {
|
|
||||||
Connection connection;
|
|
||||||
this(Connection c) {
|
|
||||||
connection = c;
|
|
||||||
super(&run);
|
|
||||||
}
|
|
||||||
|
|
||||||
void run() {
|
|
||||||
scope(exit)
|
|
||||||
connection.disconnectNow();
|
|
||||||
auto manager = new NetworkManager();
|
|
||||||
connection.parentManager = manager;
|
|
||||||
manager.addConnection(connection, connection.socket, connection.addr, connection.port);
|
|
||||||
bool breakNext = false;
|
|
||||||
while(manager.proceed()) {
|
|
||||||
if(breakNext)
|
|
||||||
break;
|
|
||||||
if(connection.disconnectQueued)
|
|
||||||
if(connection.writeBufferLength)
|
|
||||||
breakNext = true;
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
19
postgres.d
19
postgres.d
|
@ -14,8 +14,20 @@ import std.exception;
|
||||||
//
|
//
|
||||||
// SQL: `DEALLOCATE name` is how to dealloc a prepared statement.
|
// SQL: `DEALLOCATE name` is how to dealloc a prepared statement.
|
||||||
|
|
||||||
|
/++
|
||||||
|
The PostgreSql implementation of the [Database] interface.
|
||||||
|
|
||||||
|
You should construct this class, but then use it through the
|
||||||
|
interface functions.
|
||||||
|
|
||||||
|
---
|
||||||
|
auto db = new PostgreSql("dbname=name");
|
||||||
|
foreach(row; db.query("SELECT id, data FROM table_name"))
|
||||||
|
writeln(row[0], " = ", row[1]);
|
||||||
|
---
|
||||||
|
+/
|
||||||
class PostgreSql : Database {
|
class PostgreSql : Database {
|
||||||
// dbname = name is probably the most common connection string
|
/// dbname = name is probably the most common connection string
|
||||||
this(string connectionString) {
|
this(string connectionString) {
|
||||||
conn = PQconnectdb(toStringz(connectionString));
|
conn = PQconnectdb(toStringz(connectionString));
|
||||||
if(conn is null)
|
if(conn is null)
|
||||||
|
@ -29,7 +41,7 @@ class PostgreSql : Database {
|
||||||
PQfinish(conn);
|
PQfinish(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
Prepared statement support
|
Prepared statement support
|
||||||
|
|
||||||
This will be added to the Database interface eventually in some form,
|
This will be added to the Database interface eventually in some form,
|
||||||
|
@ -59,6 +71,7 @@ class PostgreSql : Database {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
override void startTransaction() {
|
override void startTransaction() {
|
||||||
query("START TRANSACTION");
|
query("START TRANSACTION");
|
||||||
}
|
}
|
||||||
|
@ -85,6 +98,7 @@ class PostgreSql : Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///
|
||||||
string error() {
|
string error() {
|
||||||
return copyCString(PQerrorMessage(conn));
|
return copyCString(PQerrorMessage(conn));
|
||||||
}
|
}
|
||||||
|
@ -93,6 +107,7 @@ class PostgreSql : Database {
|
||||||
PGconn* conn;
|
PGconn* conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
class PostgresResult : ResultSet {
|
class PostgresResult : ResultSet {
|
||||||
// name for associative array to result index
|
// name for associative array to result index
|
||||||
int getFieldIndex(string field) {
|
int getFieldIndex(string field) {
|
||||||
|
|
Loading…
Reference in New Issue