mirror of https://github.com/vitalif/phantomjs
Merge pull request #172 from KDAB/webserver-cleaned
This is the basic web server functionality. http://code.google.com/p/phantomjs/issues/detail?id=1151.4
commit
fa4391d514
|
@ -0,0 +1,52 @@
|
||||||
|
var page = require('webpage').create();
|
||||||
|
var server = require('webserver').create();
|
||||||
|
var host, port;
|
||||||
|
|
||||||
|
if (phantom.args.length !== 1) {
|
||||||
|
console.log('Usage: server.js <some port>');
|
||||||
|
phantom.exit();
|
||||||
|
} else {
|
||||||
|
port = phantom.args[0];
|
||||||
|
var listening = server.listen(port, function (request, response) {
|
||||||
|
console.log("GOT HTTP REQUEST");
|
||||||
|
console.log("request.url = " + request.url);
|
||||||
|
console.log("request.queryString = " + request.queryString);
|
||||||
|
console.log("request.method = " + request.method);
|
||||||
|
console.log("request.httpVersion = " + request.httpVersion);
|
||||||
|
console.log("request.statusCode = " + request.statusCode);
|
||||||
|
console.log("request.isSSL = " + request.isSSL);
|
||||||
|
console.log("request.remoteIP = " + request.remoteIP);
|
||||||
|
console.log("request.remotePort = " + request.remotePort);
|
||||||
|
console.log("request.remoteUser = " + request.remoteUser);
|
||||||
|
console.log("request.headers = " + request.headers);
|
||||||
|
for(var i = 0; i < request.headers; ++i) {
|
||||||
|
console.log("request.headerName(" + i + ") = " + request.headerName(i));
|
||||||
|
console.log("request.headerValue(" + i + ") = " + request.headerValue(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// we set the headers here
|
||||||
|
response.statusCode = 200;
|
||||||
|
response.headers = {"Cache": "no-cache", "Content-Type": "text/html"};
|
||||||
|
// this is also possible:
|
||||||
|
response.setHeader("foo", "bar");
|
||||||
|
// now we write the body
|
||||||
|
// note: the headers above will now be sent implictly
|
||||||
|
response.writeBody("<html><head><title>YES!</title></head>");
|
||||||
|
// note: writeBody can be called multiple times
|
||||||
|
response.writeBody("<body><p>pretty cool :)</body></html>");
|
||||||
|
});
|
||||||
|
if (!listening) {
|
||||||
|
console.log("could not create web server listening on port " + port);
|
||||||
|
phantom.exit();
|
||||||
|
}
|
||||||
|
var url = "http://localhost:" + port + "/foo/bar.php?asdf=true";
|
||||||
|
console.log(url);
|
||||||
|
page.open(url, function (status) {
|
||||||
|
if (status !== 'success') {
|
||||||
|
console.log('FAIL to load the address');
|
||||||
|
} else {
|
||||||
|
console.log(page.content);
|
||||||
|
}
|
||||||
|
phantom.exit();
|
||||||
|
});
|
||||||
|
}
|
|
@ -37,7 +37,7 @@ function require(name) {
|
||||||
|
|
||||||
var code, func, exports;
|
var code, func, exports;
|
||||||
|
|
||||||
if (name === 'webpage' || name === 'fs') {
|
if (name === 'webpage' || name === 'fs' || name == 'webserver') {
|
||||||
code = phantom.loadModuleSource(name);
|
code = phantom.loadModuleSource(name);
|
||||||
func = new Function("exports", "window", code);
|
func = new Function("exports", "window", code);
|
||||||
exports = {};
|
exports = {};
|
||||||
|
@ -55,3 +55,4 @@ function require(name) {
|
||||||
|
|
||||||
// Legacy way to use WebPage
|
// Legacy way to use WebPage
|
||||||
window.WebPage = require('webpage').create;
|
window.WebPage = require('webpage').create;
|
||||||
|
window.WebServer = require('webserver').create;
|
||||||
|
|
|
@ -37,7 +37,7 @@ function require(name) {
|
||||||
|
|
||||||
var code, func, exports;
|
var code, func, exports;
|
||||||
|
|
||||||
if (name === 'webpage' || name === 'fs') {
|
if (name === 'webpage' || name === 'fs' || name === 'webserver') {
|
||||||
code = phantom.loadModuleSource(name);
|
code = phantom.loadModuleSource(name);
|
||||||
func = new Function("exports", "window", code);
|
func = new Function("exports", "window", code);
|
||||||
exports = {};
|
exports = {};
|
||||||
|
@ -55,3 +55,4 @@ function require(name) {
|
||||||
|
|
||||||
// Legacy way to use WebPage
|
// Legacy way to use WebPage
|
||||||
window.WebPage = require('webpage').create;
|
window.WebPage = require('webpage').create;
|
||||||
|
window.WebServer = require('webserver').create;
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
/*jslint sloppy: true, nomen: true */
|
||||||
|
/*global exports:true,phantom:true */
|
||||||
|
|
||||||
|
/*
|
||||||
|
This file is part of the PhantomJS project from Ofi Labs.
|
||||||
|
|
||||||
|
Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
|
||||||
|
Copyright (C) 2011 Ivan De Marino <ivan.de.marino@gmail.com>
|
||||||
|
Copyright (C) 2011 James Roe <roejames12@hotmail.com>
|
||||||
|
Copyright (C) 2011 execjosh, http://execjosh.blogspot.com
|
||||||
|
Copyright (C) 2011 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
|
||||||
|
Author: Milian Wolff <milian.wolff@kdab.com>
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of the <organization> nor the
|
||||||
|
names of its contributors may be used to endorse or promote products
|
||||||
|
derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||||
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.create = function (opts) {
|
||||||
|
var server = phantom.createWebServer();
|
||||||
|
var handlers = {};
|
||||||
|
|
||||||
|
function checkType(o, type) {
|
||||||
|
return typeof o === type;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isObject(o) {
|
||||||
|
return checkType(o, 'object');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUndefined(o) {
|
||||||
|
return checkType(o, 'undefined');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUndefinedOrNull(o) {
|
||||||
|
return isUndefined(o) || null === o;
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyInto(target, source) {
|
||||||
|
if (target === source || isUndefinedOrNull(source)) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
target = target || {};
|
||||||
|
|
||||||
|
// Copy into objects only
|
||||||
|
if (isObject(target)) {
|
||||||
|
// Make sure source exists
|
||||||
|
source = source || {};
|
||||||
|
|
||||||
|
if (isObject(source)) {
|
||||||
|
var i, newTarget, newSource;
|
||||||
|
for (i in source) {
|
||||||
|
if (source.hasOwnProperty(i)) {
|
||||||
|
newTarget = target[i];
|
||||||
|
newSource = source[i];
|
||||||
|
|
||||||
|
if (newTarget && isObject(newSource)) {
|
||||||
|
// Deep copy
|
||||||
|
newTarget = copyInto(target[i], newSource);
|
||||||
|
} else {
|
||||||
|
newTarget = newSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isUndefined(newTarget)) {
|
||||||
|
target[i] = newTarget;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
target = source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
function defineSetter(handlerName, signalName) {
|
||||||
|
server.__defineSetter__(handlerName, function (f) {
|
||||||
|
if (handlers && typeof handlers[signalName] === 'function') {
|
||||||
|
try {
|
||||||
|
this[signalName].disconnect(handlers[signalName]);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
handlers[signalName] = f;
|
||||||
|
this[signalName].connect(handlers[signalName]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// deep copy
|
||||||
|
//TODO: use this?
|
||||||
|
// server.settings = JSON.parse(JSON.stringify(phantom.defaultServerSettings));
|
||||||
|
|
||||||
|
defineSetter("onNewRequest", "newRequest");
|
||||||
|
|
||||||
|
server.listen = function (port, handler) {
|
||||||
|
if (arguments.length === 2 && typeof handler === 'function') {
|
||||||
|
this.onNewRequest = handler;
|
||||||
|
//TODO: settings?
|
||||||
|
return this.listenOnPort(port);
|
||||||
|
}
|
||||||
|
throw "Wrong use of WebServer#listen";
|
||||||
|
};
|
||||||
|
|
||||||
|
// Copy options into server
|
||||||
|
if (opts) {
|
||||||
|
server = copyInto(server, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
return server;
|
||||||
|
};
|
|
@ -0,0 +1,6 @@
|
||||||
|
This project contains version 3.1 of the Mongoose web server project, as
|
||||||
|
found at http://code.google.com/p/mongoose.
|
||||||
|
|
||||||
|
It contains the code for version 3.1 as of 26-May-2011 (revision 0ca751520abf).
|
||||||
|
It contains an additional change in pthread_cond_broadcast() [~line 865] to
|
||||||
|
improve stability when running a debug build.
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,224 @@
|
||||||
|
// Copyright (c) 2004-2010 Sergey Lyubka
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
#ifndef MONGOOSE_HEADER_INCLUDED
|
||||||
|
#define MONGOOSE_HEADER_INCLUDED
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
struct mg_context; // Handle for the HTTP service itself
|
||||||
|
struct mg_connection; // Handle for the individual connection
|
||||||
|
|
||||||
|
|
||||||
|
// This structure contains information about the HTTP request.
|
||||||
|
struct mg_request_info {
|
||||||
|
void *user_data; // User-defined pointer passed to mg_start()
|
||||||
|
char *request_method; // "GET", "POST", etc
|
||||||
|
char *uri; // URL-decoded URI
|
||||||
|
char *http_version; // E.g. "1.0", "1.1"
|
||||||
|
char *query_string; // \0 - terminated
|
||||||
|
char *remote_user; // Authenticated user
|
||||||
|
char *log_message; // Mongoose error log message
|
||||||
|
long remote_ip; // Client's IP address
|
||||||
|
int remote_port; // Client's port
|
||||||
|
int status_code; // HTTP reply status code
|
||||||
|
int is_ssl; // 1 if SSL-ed, 0 if not
|
||||||
|
int num_headers; // Number of headers
|
||||||
|
struct mg_header {
|
||||||
|
char *name; // HTTP header name
|
||||||
|
char *value; // HTTP header value
|
||||||
|
} http_headers[64]; // Maximum 64 headers
|
||||||
|
};
|
||||||
|
|
||||||
|
// Various events on which user-defined function is called by Mongoose.
|
||||||
|
enum mg_event {
|
||||||
|
MG_NEW_REQUEST, // New HTTP request has arrived from the client
|
||||||
|
MG_HTTP_ERROR, // HTTP error must be returned to the client
|
||||||
|
MG_EVENT_LOG, // Mongoose logs an event, request_info.log_message
|
||||||
|
MG_INIT_SSL // Mongoose initializes SSL. Instead of mg_connection *,
|
||||||
|
// SSL context is passed to the callback function.
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prototype for the user-defined function. Mongoose calls this function
|
||||||
|
// on every event mentioned above.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// event: which event has been triggered.
|
||||||
|
// conn: opaque connection handler. Could be used to read, write data to the
|
||||||
|
// client, etc. See functions below that accept "mg_connection *".
|
||||||
|
// request_info: Information about HTTP request.
|
||||||
|
//
|
||||||
|
// Return:
|
||||||
|
// If handler returns non-NULL, that means that handler has processed the
|
||||||
|
// request by sending appropriate HTTP reply to the client. Mongoose treats
|
||||||
|
// the request as served.
|
||||||
|
// If callback returns NULL, that means that callback has not processed
|
||||||
|
// the request. Handler must not send any data to the client in this case.
|
||||||
|
// Mongoose proceeds with request handling as if nothing happened.
|
||||||
|
typedef void * (*mg_callback_t)(enum mg_event event,
|
||||||
|
struct mg_connection *conn,
|
||||||
|
const struct mg_request_info *request_info);
|
||||||
|
|
||||||
|
|
||||||
|
// Start web server.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// callback: user defined event handling function or NULL.
|
||||||
|
// options: NULL terminated list of option_name, option_value pairs that
|
||||||
|
// specify Mongoose configuration parameters.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// const char *options[] = {
|
||||||
|
// "document_root", "/var/www",
|
||||||
|
// "listening_ports", "80,443s",
|
||||||
|
// NULL
|
||||||
|
// };
|
||||||
|
// struct mg_context *ctx = mg_start(&my_func, NULL, options);
|
||||||
|
//
|
||||||
|
// Please refer to http://code.google.com/p/mongoose/wiki/MongooseManual
|
||||||
|
// for the list of valid option and their possible values.
|
||||||
|
//
|
||||||
|
// Return:
|
||||||
|
// web server context, or NULL on error.
|
||||||
|
struct mg_context *mg_start(mg_callback_t callback, void *user_data,
|
||||||
|
const char **options);
|
||||||
|
|
||||||
|
|
||||||
|
// Stop the web server.
|
||||||
|
//
|
||||||
|
// Must be called last, when an application wants to stop the web server and
|
||||||
|
// release all associated resources. This function blocks until all Mongoose
|
||||||
|
// threads are stopped. Context pointer becomes invalid.
|
||||||
|
void mg_stop(struct mg_context *);
|
||||||
|
|
||||||
|
|
||||||
|
// Get the value of particular configuration parameter.
|
||||||
|
// The value returned is read-only. Mongoose does not allow changing
|
||||||
|
// configuration at run time.
|
||||||
|
// If given parameter name is not valid, NULL is returned. For valid
|
||||||
|
// names, return value is guaranteed to be non-NULL. If parameter is not
|
||||||
|
// set, zero-length string is returned.
|
||||||
|
const char *mg_get_option(const struct mg_context *ctx, const char *name);
|
||||||
|
|
||||||
|
|
||||||
|
// Return array of strings that represent valid configuration options.
|
||||||
|
// For each option, a short name, long name, and default value is returned.
|
||||||
|
// Array is NULL terminated.
|
||||||
|
const char **mg_get_valid_option_names(void);
|
||||||
|
|
||||||
|
|
||||||
|
// Add, edit or delete the entry in the passwords file.
|
||||||
|
//
|
||||||
|
// This function allows an application to manipulate .htpasswd files on the
|
||||||
|
// fly by adding, deleting and changing user records. This is one of the
|
||||||
|
// several ways of implementing authentication on the server side. For another,
|
||||||
|
// cookie-based way please refer to the examples/chat.c in the source tree.
|
||||||
|
//
|
||||||
|
// If password is not NULL, entry is added (or modified if already exists).
|
||||||
|
// If password is NULL, entry is deleted.
|
||||||
|
//
|
||||||
|
// Return:
|
||||||
|
// 1 on success, 0 on error.
|
||||||
|
int mg_modify_passwords_file(const char *passwords_file_name,
|
||||||
|
const char *domain,
|
||||||
|
const char *user,
|
||||||
|
const char *password);
|
||||||
|
|
||||||
|
// Send data to the client.
|
||||||
|
int mg_write(struct mg_connection *, const void *buf, size_t len);
|
||||||
|
|
||||||
|
|
||||||
|
// Send data to the browser using printf() semantics.
|
||||||
|
//
|
||||||
|
// Works exactly like mg_write(), but allows to do message formatting.
|
||||||
|
// Note that mg_printf() uses internal buffer of size IO_BUF_SIZE
|
||||||
|
// (8 Kb by default) as temporary message storage for formatting. Do not
|
||||||
|
// print data that is bigger than that, otherwise it will be truncated.
|
||||||
|
int mg_printf(struct mg_connection *, const char *fmt, ...);
|
||||||
|
|
||||||
|
|
||||||
|
// Read data from the remote end, return number of bytes read.
|
||||||
|
int mg_read(struct mg_connection *, void *buf, size_t len);
|
||||||
|
|
||||||
|
|
||||||
|
// Get the value of particular HTTP header.
|
||||||
|
//
|
||||||
|
// This is a helper function. It traverses request_info->http_headers array,
|
||||||
|
// and if the header is present in the array, returns its value. If it is
|
||||||
|
// not present, NULL is returned.
|
||||||
|
const char *mg_get_header(const struct mg_connection *, const char *name);
|
||||||
|
|
||||||
|
|
||||||
|
// Get a value of particular form variable.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// data: pointer to form-uri-encoded buffer. This could be either POST data,
|
||||||
|
// or request_info.query_string.
|
||||||
|
// data_len: length of the encoded data.
|
||||||
|
// var_name: variable name to decode from the buffer
|
||||||
|
// buf: destination buffer for the decoded variable
|
||||||
|
// buf_len: length of the destination buffer
|
||||||
|
//
|
||||||
|
// Return:
|
||||||
|
// On success, length of the decoded variable.
|
||||||
|
// On error, -1 (variable not found, or destination buffer is too small).
|
||||||
|
//
|
||||||
|
// Destination buffer is guaranteed to be '\0' - terminated. In case of
|
||||||
|
// failure, dst[0] == '\0'.
|
||||||
|
int mg_get_var(const char *data, size_t data_len,
|
||||||
|
const char *var_name, char *buf, size_t buf_len);
|
||||||
|
|
||||||
|
// Fetch value of certain cookie variable into the destination buffer.
|
||||||
|
//
|
||||||
|
// Destination buffer is guaranteed to be '\0' - terminated. In case of
|
||||||
|
// failure, dst[0] == '\0'. Note that RFC allows many occurrences of the same
|
||||||
|
// parameter. This function returns only first occurrence.
|
||||||
|
//
|
||||||
|
// Return:
|
||||||
|
// On success, value length.
|
||||||
|
// On error, -1 (either "Cookie:" header is not present at all, or the
|
||||||
|
// requested parameter is not found, or destination buffer is too small
|
||||||
|
// to hold the value).
|
||||||
|
int mg_get_cookie(const struct mg_connection *,
|
||||||
|
const char *cookie_name, char *buf, size_t buf_len);
|
||||||
|
|
||||||
|
|
||||||
|
// Return Mongoose version.
|
||||||
|
const char *mg_version(void);
|
||||||
|
|
||||||
|
|
||||||
|
// MD5 hash given strings.
|
||||||
|
// Buffer 'buf' must be 33 bytes long. Varargs is a NULL terminated list of
|
||||||
|
// asciiz strings. When function returns, buf will contain human-readable
|
||||||
|
// MD5 hash. Example:
|
||||||
|
// char buf[33];
|
||||||
|
// mg_md5(buf, "aa", "bb", NULL);
|
||||||
|
void mg_md5(char *buf, ...);
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
#endif // MONGOOSE_HEADER_INCLUDED
|
|
@ -0,0 +1,5 @@
|
||||||
|
VPATH += $$PWD
|
||||||
|
INCLUDEPATH += $$PWD
|
||||||
|
|
||||||
|
SOURCES += mongoose.c
|
||||||
|
HEADERS += mongoose.h
|
|
@ -40,6 +40,7 @@
|
||||||
#include "terminal.h"
|
#include "terminal.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "webpage.h"
|
#include "webpage.h"
|
||||||
|
#include "webserver.h"
|
||||||
|
|
||||||
|
|
||||||
// public:
|
// public:
|
||||||
|
@ -185,6 +186,16 @@ QObject *Phantom::createWebPage()
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QObject* Phantom::createWebServer()
|
||||||
|
{
|
||||||
|
WebServer *server = new WebServer(this, &m_config);
|
||||||
|
m_servers.append(server);
|
||||||
|
///TODO:
|
||||||
|
// page->applySettings(m_defaultPageSettings);
|
||||||
|
// page->setLibraryPath(QFileInfo(m_config.scriptFile()).dir().absolutePath());
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
QObject *Phantom::createFilesystem()
|
QObject *Phantom::createFilesystem()
|
||||||
{
|
{
|
||||||
if (!m_filesystem)
|
if (!m_filesystem)
|
||||||
|
@ -215,6 +226,8 @@ void Phantom::exit(int code)
|
||||||
qDeleteAll(m_pages);
|
qDeleteAll(m_pages);
|
||||||
m_pages.clear();
|
m_pages.clear();
|
||||||
m_page = 0;
|
m_page = 0;
|
||||||
|
qDeleteAll(m_servers);
|
||||||
|
m_servers.clear();
|
||||||
QApplication::instance()->exit(code);
|
QApplication::instance()->exit(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
#include <QtGui>
|
#include <QtGui>
|
||||||
|
|
||||||
class WebPage;
|
class WebPage;
|
||||||
|
class WebServer;
|
||||||
#include "csconverter.h"
|
#include "csconverter.h"
|
||||||
#include "filesystem.h"
|
#include "filesystem.h"
|
||||||
#include "encoding.h"
|
#include "encoding.h"
|
||||||
|
@ -71,6 +72,7 @@ public:
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
QObject *createWebPage();
|
QObject *createWebPage();
|
||||||
|
QObject *createWebServer();
|
||||||
QObject *createFilesystem();
|
QObject *createFilesystem();
|
||||||
QString loadModuleSource(const QString &name);
|
QString loadModuleSource(const QString &name);
|
||||||
bool injectJs(const QString &jsFilePath);
|
bool injectJs(const QString &jsFilePath);
|
||||||
|
@ -88,6 +90,7 @@ private:
|
||||||
QVariantMap m_defaultPageSettings;
|
QVariantMap m_defaultPageSettings;
|
||||||
FileSystem *m_filesystem;
|
FileSystem *m_filesystem;
|
||||||
QList<QPointer<WebPage> > m_pages;
|
QList<QPointer<WebPage> > m_pages;
|
||||||
|
QList<QPointer<WebServer> > m_servers;
|
||||||
Config m_config;
|
Config m_config;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ RESOURCES = phantomjs.qrc
|
||||||
HEADERS += csconverter.h \
|
HEADERS += csconverter.h \
|
||||||
phantom.h \
|
phantom.h \
|
||||||
webpage.h \
|
webpage.h \
|
||||||
|
webserver.h \
|
||||||
consts.h \
|
consts.h \
|
||||||
utils.h \
|
utils.h \
|
||||||
networkaccessmanager.h \
|
networkaccessmanager.h \
|
||||||
|
@ -21,8 +22,10 @@ HEADERS += csconverter.h \
|
||||||
terminal.h \
|
terminal.h \
|
||||||
encoding.h \
|
encoding.h \
|
||||||
config.h
|
config.h
|
||||||
|
|
||||||
SOURCES += phantom.cpp \
|
SOURCES += phantom.cpp \
|
||||||
webpage.cpp \
|
webpage.cpp \
|
||||||
|
webserver.cpp \
|
||||||
main.cpp \
|
main.cpp \
|
||||||
csconverter.cpp \
|
csconverter.cpp \
|
||||||
utils.cpp \
|
utils.cpp \
|
||||||
|
@ -37,9 +40,11 @@ OTHER_FILES += usage.txt \
|
||||||
bootstrap.js \
|
bootstrap.js \
|
||||||
configurator.js \
|
configurator.js \
|
||||||
modules/fs.js \
|
modules/fs.js \
|
||||||
modules/webpage.js
|
modules/webpage.js \
|
||||||
|
modules/webserver.js
|
||||||
|
|
||||||
include(gif/gif.pri)
|
include(gif/gif.pri)
|
||||||
|
include(mongoose/mongoose.pri)
|
||||||
|
|
||||||
win32: RC_FILE = phantomjs_win.rc
|
win32: RC_FILE = phantomjs_win.rc
|
||||||
os2: RC_FILE = phantomjs_os2.rc
|
os2: RC_FILE = phantomjs_os2.rc
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
<file>bootstrap.js</file>
|
<file>bootstrap.js</file>
|
||||||
<file>configurator.js</file>
|
<file>configurator.js</file>
|
||||||
<file>modules/webpage.js</file>
|
<file>modules/webpage.js</file>
|
||||||
|
<file>modules/webserver.js</file>
|
||||||
<file>modules/fs.js</file>
|
<file>modules/fs.js</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
|
@ -0,0 +1,358 @@
|
||||||
|
/*
|
||||||
|
This file is part of the PhantomJS project from Ofi Labs.
|
||||||
|
|
||||||
|
Copyright (C) 2011 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
|
||||||
|
Author: Milian Wolff <milian.wolff@kdab.com>
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of the <organization> nor the
|
||||||
|
names of its contributors may be used to endorse or promote products
|
||||||
|
derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||||
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "webserver.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include "mongoose.h"
|
||||||
|
#include <QThread>
|
||||||
|
#include <QHostAddress>
|
||||||
|
|
||||||
|
static void *callback(mg_event event,
|
||||||
|
mg_connection *conn,
|
||||||
|
const mg_request_info *request_info)
|
||||||
|
{
|
||||||
|
WebServer* server = static_cast<WebServer*>(request_info->user_data);
|
||||||
|
// note: we use a blocking queued connection to always handle the request in the main thread
|
||||||
|
// TODO: check whether direct call works as well
|
||||||
|
bool handled = false;
|
||||||
|
Qt::ConnectionType connectionType = Qt::DirectConnection;
|
||||||
|
if (QThread::currentThread() != server->thread()) {
|
||||||
|
connectionType = Qt::BlockingQueuedConnection;
|
||||||
|
}
|
||||||
|
QMetaObject::invokeMethod(server, "handleRequest", connectionType,
|
||||||
|
Q_ARG(mg_event, event), Q_ARG(mg_connection*, conn),
|
||||||
|
Q_ARG(const mg_request_info*, request_info),
|
||||||
|
Q_ARG(bool*, &handled));
|
||||||
|
if (handled) {
|
||||||
|
// anything non-null... pretty ugly, why not simply a bool??
|
||||||
|
return server;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WebServer::WebServer(QObject *parent, const Config *config)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_config(m_config)
|
||||||
|
, m_ctx(0)
|
||||||
|
{
|
||||||
|
setObjectName("WebServer");
|
||||||
|
}
|
||||||
|
|
||||||
|
WebServer::~WebServer()
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WebServer::listenOnPort(const QString& port)
|
||||||
|
{
|
||||||
|
///TODO: listen on multiple ports?
|
||||||
|
close();
|
||||||
|
|
||||||
|
const char *options[] = {"listening_ports", qstrdup(qPrintable(port)), NULL};
|
||||||
|
///TODO: more options from m_config?
|
||||||
|
m_ctx = mg_start(&callback, this, options);
|
||||||
|
if (!m_ctx) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_port = port;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WebServer::port() const
|
||||||
|
{
|
||||||
|
return m_port;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebServer::close()
|
||||||
|
{
|
||||||
|
if (m_ctx) {
|
||||||
|
mg_stop(m_ctx);
|
||||||
|
m_ctx = 0;
|
||||||
|
m_port.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebServer::handleRequest(mg_event event, mg_connection *conn, const mg_request_info *request,
|
||||||
|
bool *handled)
|
||||||
|
{
|
||||||
|
Q_ASSERT(QThread::currentThread() == thread());
|
||||||
|
if (event == MG_NEW_REQUEST) {
|
||||||
|
WebServerRequest requestObj(request);
|
||||||
|
WebServerResponse responseObj(conn);
|
||||||
|
emit newRequest(&requestObj, &responseObj);
|
||||||
|
*handled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*handled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//BEGIN WebServerRequest
|
||||||
|
WebServerRequest::WebServerRequest(const mg_request_info *request)
|
||||||
|
: m_request(request)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WebServerRequest::method() const
|
||||||
|
{
|
||||||
|
///TODO: encoding?!
|
||||||
|
return QString::fromLocal8Bit(m_request->request_method);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WebServerRequest::httpVersion() const
|
||||||
|
{
|
||||||
|
///TODO: encoding?!
|
||||||
|
return QString::fromLocal8Bit(m_request->http_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
int WebServerRequest::statusCode() const
|
||||||
|
{
|
||||||
|
return m_request->status_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WebServerRequest::isSSL() const
|
||||||
|
{
|
||||||
|
return m_request->is_ssl;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WebServerRequest::url() const
|
||||||
|
{
|
||||||
|
///TODO: encoding?!
|
||||||
|
return QString::fromLocal8Bit(m_request->uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WebServerRequest::queryString() const
|
||||||
|
{
|
||||||
|
///TODO: encoding?!
|
||||||
|
return QString::fromLocal8Bit(m_request->query_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WebServerRequest::remoteIP() const
|
||||||
|
{
|
||||||
|
return QHostAddress(m_request->remote_ip).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
int WebServerRequest::remotePort() const
|
||||||
|
{
|
||||||
|
return m_request->remote_port;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WebServerRequest::remoteUser() const
|
||||||
|
{
|
||||||
|
///TODO: encoding?!
|
||||||
|
return QString::fromLocal8Bit(m_request->remote_user);
|
||||||
|
}
|
||||||
|
|
||||||
|
int WebServerRequest::headers() const
|
||||||
|
{
|
||||||
|
return m_request->num_headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WebServerRequest::headerName(int header) const
|
||||||
|
{
|
||||||
|
Q_ASSERT(header >= 0 && header < m_request->num_headers);
|
||||||
|
///TODO: encoding?!
|
||||||
|
return QString::fromLocal8Bit(m_request->http_headers[header].name);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WebServerRequest::headerValue(int header) const
|
||||||
|
{
|
||||||
|
Q_ASSERT(header >= 0 && header < m_request->num_headers);
|
||||||
|
///TODO: encoding?!
|
||||||
|
return QString::fromLocal8Bit(m_request->http_headers[header].value);
|
||||||
|
}
|
||||||
|
|
||||||
|
//END WebServerRequest
|
||||||
|
|
||||||
|
//BEGIN WebServerResponse
|
||||||
|
|
||||||
|
WebServerResponse::WebServerResponse(mg_connection *conn)
|
||||||
|
: m_conn(conn)
|
||||||
|
, m_statusCode(200)
|
||||||
|
, m_headersSent(false)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* responseCodeString(int code)
|
||||||
|
{
|
||||||
|
// see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
||||||
|
switch (code) {
|
||||||
|
case 100:
|
||||||
|
return "Continue";
|
||||||
|
case 101:
|
||||||
|
return "Switching Protocols";
|
||||||
|
case 200:
|
||||||
|
return "OK";
|
||||||
|
case 201:
|
||||||
|
return "Created";
|
||||||
|
case 202:
|
||||||
|
return "Accepted";
|
||||||
|
case 203:
|
||||||
|
return "Non-Authoritative Information";
|
||||||
|
case 204:
|
||||||
|
return "No Content";
|
||||||
|
case 205:
|
||||||
|
return "Reset Content";
|
||||||
|
case 206:
|
||||||
|
return "Partial Content";
|
||||||
|
case 300:
|
||||||
|
return "Multiple Choices";
|
||||||
|
case 301:
|
||||||
|
return "Moved Permanently";
|
||||||
|
case 302:
|
||||||
|
return "Found";
|
||||||
|
case 303:
|
||||||
|
return "See Other";
|
||||||
|
case 304:
|
||||||
|
return "Not Modified";
|
||||||
|
case 305:
|
||||||
|
return "Use Proxy";
|
||||||
|
case 307:
|
||||||
|
return "Temporary Redirect";
|
||||||
|
case 400:
|
||||||
|
return "Bad Request";
|
||||||
|
case 401:
|
||||||
|
return "Unauthorized";
|
||||||
|
case 402:
|
||||||
|
return "Payment Required";
|
||||||
|
case 403:
|
||||||
|
return "Forbidden";
|
||||||
|
case 404:
|
||||||
|
return "Not Found";
|
||||||
|
case 405:
|
||||||
|
return "Method Not Allowed";
|
||||||
|
case 406:
|
||||||
|
return "Not Acceptable";
|
||||||
|
case 407:
|
||||||
|
return "Proxy Authentication Required";
|
||||||
|
case 408:
|
||||||
|
return "Request Timeout";
|
||||||
|
case 409:
|
||||||
|
return "Conflict";
|
||||||
|
case 410:
|
||||||
|
return "Gone";
|
||||||
|
case 411:
|
||||||
|
return "Length Required";
|
||||||
|
case 412:
|
||||||
|
return "Precondition Failed";
|
||||||
|
case 413:
|
||||||
|
return "Request Entity Too Large";
|
||||||
|
case 414:
|
||||||
|
return "Request-URI Too Long";
|
||||||
|
case 415:
|
||||||
|
return "Unsupported Media Type";
|
||||||
|
case 416:
|
||||||
|
return "Requested Range not Satisfiable";
|
||||||
|
case 417:
|
||||||
|
return "Expectation Failed";
|
||||||
|
case 500:
|
||||||
|
return "Internal Server Error";
|
||||||
|
case 501:
|
||||||
|
return "Not Implemented";
|
||||||
|
case 502:
|
||||||
|
return "Bad Gateway";
|
||||||
|
case 503:
|
||||||
|
return "Service Unavailable";
|
||||||
|
case 504:
|
||||||
|
return "Gateway Timeout";
|
||||||
|
case 505:
|
||||||
|
return "HTTP Version Not Supported";
|
||||||
|
case 306:
|
||||||
|
// unused: fallthrough
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebServerResponse::writeHeaders(int statusCode, const QVariantMap &headers)
|
||||||
|
{
|
||||||
|
///TODO: what is the best-practice error handling in javascript? exceptions?
|
||||||
|
Q_ASSERT(!m_headersSent);
|
||||||
|
m_headersSent = true;
|
||||||
|
mg_printf(m_conn, "HTTP/1.1 %d %s\r\n", m_statusCode, responseCodeString(m_statusCode));
|
||||||
|
QVariantMap::const_iterator it = headers.constBegin();
|
||||||
|
while(it != headers.constEnd()) {
|
||||||
|
mg_printf(m_conn, "%s: %s\r\n", qPrintable(it.key()), qPrintable(it.value().toString()));
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
mg_write(m_conn, "\r\n", 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebServerResponse::writeBody(const QString &body)
|
||||||
|
{
|
||||||
|
if (!m_headersSent) {
|
||||||
|
writeHeaders(m_statusCode, m_headers);
|
||||||
|
}
|
||||||
|
///TODO: encoding?!
|
||||||
|
const QByteArray data = body.toLocal8Bit();
|
||||||
|
mg_write(m_conn, data.constData(), data.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
int WebServerResponse::statusCode() const
|
||||||
|
{
|
||||||
|
return m_statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebServerResponse::setStatusCode(int code)
|
||||||
|
{
|
||||||
|
///TODO: what is the best-practice error handling in javascript? exceptions?
|
||||||
|
Q_ASSERT(!m_headersSent);
|
||||||
|
m_statusCode = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WebServerResponse::header(const QString &name) const
|
||||||
|
{
|
||||||
|
return m_headers.value(name).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebServerResponse::setHeader(const QString &name, const QString &value)
|
||||||
|
{
|
||||||
|
///TODO: what is the best-practice error handling in javascript? exceptions?
|
||||||
|
Q_ASSERT(!m_headersSent);
|
||||||
|
m_headers.insert(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantMap WebServerResponse::headers() const
|
||||||
|
{
|
||||||
|
return m_headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebServerResponse::setHeaders(const QVariantMap &headers)
|
||||||
|
{
|
||||||
|
///TODO: what is the best-practice error handling in javascript? exceptions?
|
||||||
|
Q_ASSERT(!m_headersSent);
|
||||||
|
m_headers = headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
//END WebServerResponse
|
|
@ -0,0 +1,177 @@
|
||||||
|
/*
|
||||||
|
This file is part of the PhantomJS project from Ofi Labs.
|
||||||
|
|
||||||
|
Copyright (C) 2011 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
|
||||||
|
Author: Milian Wolff <milian.wolff@kdab.com>
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of the <organization> nor the
|
||||||
|
names of its contributors may be used to endorse or promote products
|
||||||
|
derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||||
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef WEBSERVER_H
|
||||||
|
#define WEBSERVER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QVariantMap>
|
||||||
|
|
||||||
|
///TODO: is this ok, or should it be put into .cpp
|
||||||
|
/// can be done by introducing a WebServerPrivate *d;
|
||||||
|
#include "mongoose.h"
|
||||||
|
|
||||||
|
class Config;
|
||||||
|
|
||||||
|
class WebServerRequest;
|
||||||
|
class WebServerResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scriptable HTTP web server.
|
||||||
|
*
|
||||||
|
* see also: modules/webserver.js
|
||||||
|
*/
|
||||||
|
class WebServer : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(QString port READ port);
|
||||||
|
|
||||||
|
public:
|
||||||
|
WebServer(QObject *parent, const Config *config);
|
||||||
|
virtual ~WebServer();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
/**
|
||||||
|
* Start listening for incoming connections on port @p port.
|
||||||
|
*
|
||||||
|
* For each new request @c handleRequest() will be called which
|
||||||
|
* in turn emits @c newRequest() where appropriate.
|
||||||
|
*
|
||||||
|
* @return true if we can listen on @p port, false otherwise.
|
||||||
|
*
|
||||||
|
* WARNING: must not be the same name as in the javascript api...
|
||||||
|
*/
|
||||||
|
bool listenOnPort(const QString &port);
|
||||||
|
/**
|
||||||
|
* @return the port this server is listening on
|
||||||
|
* or an empty string if the server is closed.
|
||||||
|
*/
|
||||||
|
QString port() const;
|
||||||
|
|
||||||
|
/// Stop listening for incoming connections.
|
||||||
|
void close();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
/// @p request is a WebServerRequest, @p response is a WebServerResponse
|
||||||
|
void newRequest(QObject *request, QObject *response);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void handleRequest(mg_event event, mg_connection* conn, const mg_request_info* request,
|
||||||
|
bool* handled);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Config *m_config;
|
||||||
|
mg_context *m_ctx;
|
||||||
|
QString m_port;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Incoming HTTP client request.
|
||||||
|
*/
|
||||||
|
class WebServerRequest : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(QString method READ method)
|
||||||
|
Q_PROPERTY(QString httpVersion READ httpVersion)
|
||||||
|
Q_PROPERTY(int statusCode READ statusCode)
|
||||||
|
Q_PROPERTY(bool isSSL READ isSSL)
|
||||||
|
Q_PROPERTY(QString url READ url)
|
||||||
|
Q_PROPERTY(QString queryString READ queryString)
|
||||||
|
Q_PROPERTY(QString remoteIP READ remoteIP)
|
||||||
|
Q_PROPERTY(int remotePort READ remotePort)
|
||||||
|
Q_PROPERTY(QString remoteUser READ remoteUser)
|
||||||
|
Q_PROPERTY(int headers READ headers)
|
||||||
|
|
||||||
|
public:
|
||||||
|
WebServerRequest(const mg_request_info *request);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
/// @return request method, i.e. Get/Post
|
||||||
|
QString method() const;
|
||||||
|
QString httpVersion() const;
|
||||||
|
int statusCode() const;
|
||||||
|
bool isSSL() const;
|
||||||
|
QString url() const;
|
||||||
|
QString queryString() const;
|
||||||
|
QString remoteIP() const;
|
||||||
|
int remotePort() const;
|
||||||
|
QString remoteUser() const;
|
||||||
|
|
||||||
|
//TODO: better javascript api that allows easy "for(i in headers)" iteration?
|
||||||
|
//see e.g.: http://www.qtcentre.org/threads/31298-QtScript-bindings-to-advanced-containers-%28QMap-QList-etc%29
|
||||||
|
int headers() const;
|
||||||
|
QString headerName(int header) const;
|
||||||
|
QString headerValue(int header) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const mg_request_info *m_request;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outgoing HTTP response to client.
|
||||||
|
*/
|
||||||
|
class WebServerResponse : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PROPERTY(int statusCode READ statusCode WRITE setStatusCode);
|
||||||
|
Q_PROPERTY(QVariantMap headers READ headers WRITE setHeaders);
|
||||||
|
public:
|
||||||
|
WebServerResponse(mg_connection *conn);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
/// send @p headers to client with status code @p statusCode
|
||||||
|
void writeHeaders(int statusCode, const QVariantMap &headers);
|
||||||
|
/// sends @p data to client and makes sure the headers are send beforehand
|
||||||
|
void writeBody(const QString &data);
|
||||||
|
|
||||||
|
/// get the currently set status code, 200 is the default
|
||||||
|
int statusCode() const;
|
||||||
|
/// set the status code to @p code
|
||||||
|
void setStatusCode(int code);
|
||||||
|
|
||||||
|
/// get the value of header @p name
|
||||||
|
QString header(const QString &name) const;
|
||||||
|
/// set the value of header @p name to @p value
|
||||||
|
void setHeader(const QString &name, const QString &value);
|
||||||
|
|
||||||
|
/// get all headers
|
||||||
|
QVariantMap headers() const;
|
||||||
|
/// set all headers
|
||||||
|
void setHeaders(const QVariantMap &headers);
|
||||||
|
|
||||||
|
private:
|
||||||
|
mg_connection *m_conn;
|
||||||
|
int m_statusCode;
|
||||||
|
QVariantMap m_headers;
|
||||||
|
bool m_headersSent;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // WEBSERVER_H
|
|
@ -4,6 +4,10 @@ describe("module loading using require", function() {
|
||||||
expect(typeof require('webpage')).toEqual('object');
|
expect(typeof require('webpage')).toEqual('object');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should work for 'webserver' module", function() {
|
||||||
|
expect(typeof require('webserver')).toEqual('object');
|
||||||
|
});
|
||||||
|
|
||||||
it("should work for 'fs' module", function() {
|
it("should work for 'fs' module", function() {
|
||||||
expect(typeof require('fs')).toEqual('object');
|
expect(typeof require('fs')).toEqual('object');
|
||||||
});
|
});
|
||||||
|
|
|
@ -58,6 +58,7 @@ var fs = require('fs');
|
||||||
phantom.injectJs("./phantom-spec.js");
|
phantom.injectJs("./phantom-spec.js");
|
||||||
phantom.injectJs("./module-spec.js");
|
phantom.injectJs("./module-spec.js");
|
||||||
phantom.injectJs("./webpage-spec.js");
|
phantom.injectJs("./webpage-spec.js");
|
||||||
|
phantom.injectJs("./webserver-spec.js");
|
||||||
phantom.injectJs("./fs-spec-01.js"); //< Filesystem Specs 01 (Basic)
|
phantom.injectJs("./fs-spec-01.js"); //< Filesystem Specs 01 (Basic)
|
||||||
phantom.injectJs("./fs-spec-02.js"); //< Filesystem Specs 02 (Attributes)
|
phantom.injectJs("./fs-spec-02.js"); //< Filesystem Specs 02 (Attributes)
|
||||||
phantom.injectJs("./fs-spec-03.js"); //< Filesystem Specs 03 (Paths)
|
phantom.injectJs("./fs-spec-03.js"); //< Filesystem Specs 03 (Paths)
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
describe("WebServer constructor", function() {
|
||||||
|
it("should exist in window", function() {
|
||||||
|
expect(window.hasOwnProperty('WebServer')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be a function", function() {
|
||||||
|
expect(typeof window.WebServer).toEqual('function');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function checkRequest(request, response) {
|
||||||
|
expect(typeof request).toEqual('object');
|
||||||
|
expectHasProperty(request, 'url');
|
||||||
|
expectHasProperty(request, 'queryString');
|
||||||
|
expectHasProperty(request, 'method');
|
||||||
|
expectHasProperty(request, 'httpVersion');
|
||||||
|
expectHasProperty(request, 'statusCode');
|
||||||
|
expectHasProperty(request, 'isSSL');
|
||||||
|
expectHasProperty(request, 'remoteIP');
|
||||||
|
expectHasProperty(request, 'remotePort');
|
||||||
|
expectHasProperty(request, 'remoteUser');
|
||||||
|
expectHasProperty(request, 'headers');
|
||||||
|
expectHasProperty(request, 'headerName');
|
||||||
|
expectHasProperty(request, 'headerValue');
|
||||||
|
|
||||||
|
expect(typeof response).toEqual('object');
|
||||||
|
expectHasProperty(response, 'statusCode');
|
||||||
|
expectHasProperty(response, 'headers');
|
||||||
|
expectHasFunction(response, 'setHeader');
|
||||||
|
expectHasFunction(response, 'writeBody');
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("WebServer object", function() {
|
||||||
|
var server = new WebServer();
|
||||||
|
|
||||||
|
it("should be creatable", function() {
|
||||||
|
expect(typeof server).toEqual('object');
|
||||||
|
expect(server).toNotEqual(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("should have objectName as 'WebServer'", function() {
|
||||||
|
expect(server.objectName).toEqual('WebServer');
|
||||||
|
});
|
||||||
|
|
||||||
|
expectHasProperty(server, 'port');
|
||||||
|
it("should have port as string", function() {
|
||||||
|
expect(typeof server.port).toEqual('string');
|
||||||
|
});
|
||||||
|
it("should not listen to any port by default", function() {
|
||||||
|
expect(server.port).toEqual("");
|
||||||
|
});
|
||||||
|
|
||||||
|
/* TODO:
|
||||||
|
expectHasProperty(page, 'settings');
|
||||||
|
it("should have non-empty settings", function() {
|
||||||
|
expect(page.settings).toNotEqual(null);
|
||||||
|
expect(page.settings).toNotEqual({});
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
expectHasFunction(server, 'listenOnPort');
|
||||||
|
expectHasFunction(server, 'newRequest');
|
||||||
|
expectHasFunction(server, 'close');
|
||||||
|
|
||||||
|
it("should fail to listen to blocked ports", function() {
|
||||||
|
//NOTE: is this really blocked everywhere?
|
||||||
|
expect(server.listen(1, function(){})).toEqual(false);
|
||||||
|
expect(server.port).toEqual("");
|
||||||
|
});
|
||||||
|
it("should be able to listen to some port", function() {
|
||||||
|
//NOTE: this can fail if the port is already being listend on...
|
||||||
|
expect(server.listen(12345, checkRequest)).toEqual(true);
|
||||||
|
expect(server.port).toEqual("12345");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle requests", function() {
|
||||||
|
var page = require('webpage').create();
|
||||||
|
var url = "http://localhost:12345/foo/bar.php?asdf=true";
|
||||||
|
page.open(url, function (status) {
|
||||||
|
expect(status == 'success').toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue