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;
|
||||
|
||||
if (name === 'webpage' || name === 'fs') {
|
||||
if (name === 'webpage' || name === 'fs' || name == 'webserver') {
|
||||
code = phantom.loadModuleSource(name);
|
||||
func = new Function("exports", "window", code);
|
||||
exports = {};
|
||||
|
@ -55,3 +55,4 @@ function require(name) {
|
|||
|
||||
// Legacy way to use WebPage
|
||||
window.WebPage = require('webpage').create;
|
||||
window.WebServer = require('webserver').create;
|
||||
|
|
|
@ -37,7 +37,7 @@ function require(name) {
|
|||
|
||||
var code, func, exports;
|
||||
|
||||
if (name === 'webpage' || name === 'fs') {
|
||||
if (name === 'webpage' || name === 'fs' || name === 'webserver') {
|
||||
code = phantom.loadModuleSource(name);
|
||||
func = new Function("exports", "window", code);
|
||||
exports = {};
|
||||
|
@ -55,3 +55,4 @@ function require(name) {
|
|||
|
||||
// Legacy way to use WebPage
|
||||
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 "utils.h"
|
||||
#include "webpage.h"
|
||||
#include "webserver.h"
|
||||
|
||||
|
||||
// public:
|
||||
|
@ -185,6 +186,16 @@ QObject *Phantom::createWebPage()
|
|||
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()
|
||||
{
|
||||
if (!m_filesystem)
|
||||
|
@ -215,6 +226,8 @@ void Phantom::exit(int code)
|
|||
qDeleteAll(m_pages);
|
||||
m_pages.clear();
|
||||
m_page = 0;
|
||||
qDeleteAll(m_servers);
|
||||
m_servers.clear();
|
||||
QApplication::instance()->exit(code);
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include <QtGui>
|
||||
|
||||
class WebPage;
|
||||
class WebServer;
|
||||
#include "csconverter.h"
|
||||
#include "filesystem.h"
|
||||
#include "encoding.h"
|
||||
|
@ -71,6 +72,7 @@ public:
|
|||
|
||||
public slots:
|
||||
QObject *createWebPage();
|
||||
QObject *createWebServer();
|
||||
QObject *createFilesystem();
|
||||
QString loadModuleSource(const QString &name);
|
||||
bool injectJs(const QString &jsFilePath);
|
||||
|
@ -88,6 +90,7 @@ private:
|
|||
QVariantMap m_defaultPageSettings;
|
||||
FileSystem *m_filesystem;
|
||||
QList<QPointer<WebPage> > m_pages;
|
||||
QList<QPointer<WebServer> > m_servers;
|
||||
Config m_config;
|
||||
};
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ RESOURCES = phantomjs.qrc
|
|||
HEADERS += csconverter.h \
|
||||
phantom.h \
|
||||
webpage.h \
|
||||
webserver.h \
|
||||
consts.h \
|
||||
utils.h \
|
||||
networkaccessmanager.h \
|
||||
|
@ -21,8 +22,10 @@ HEADERS += csconverter.h \
|
|||
terminal.h \
|
||||
encoding.h \
|
||||
config.h
|
||||
|
||||
SOURCES += phantom.cpp \
|
||||
webpage.cpp \
|
||||
webserver.cpp \
|
||||
main.cpp \
|
||||
csconverter.cpp \
|
||||
utils.cpp \
|
||||
|
@ -37,9 +40,11 @@ OTHER_FILES += usage.txt \
|
|||
bootstrap.js \
|
||||
configurator.js \
|
||||
modules/fs.js \
|
||||
modules/webpage.js
|
||||
modules/webpage.js \
|
||||
modules/webserver.js
|
||||
|
||||
include(gif/gif.pri)
|
||||
include(mongoose/mongoose.pri)
|
||||
|
||||
win32: RC_FILE = phantomjs_win.rc
|
||||
os2: RC_FILE = phantomjs_os2.rc
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<file>bootstrap.js</file>
|
||||
<file>configurator.js</file>
|
||||
<file>modules/webpage.js</file>
|
||||
<file>modules/webserver.js</file>
|
||||
<file>modules/fs.js</file>
|
||||
</qresource>
|
||||
</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');
|
||||
});
|
||||
|
||||
it("should work for 'webserver' module", function() {
|
||||
expect(typeof require('webserver')).toEqual('object');
|
||||
});
|
||||
|
||||
it("should work for 'fs' module", function() {
|
||||
expect(typeof require('fs')).toEqual('object');
|
||||
});
|
||||
|
|
|
@ -58,6 +58,7 @@ var fs = require('fs');
|
|||
phantom.injectJs("./phantom-spec.js");
|
||||
phantom.injectJs("./module-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-02.js"); //< Filesystem Specs 02 (Attributes)
|
||||
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