125 Commits

Author SHA1 Message Date
Nathan Osman
43f55df516 Fix link type mismatch on Windows and build a shared library by default. 2018-03-22 13:14:00 -07:00
Nathan Osman
f70405e4d6 Merge pull request #25 from tonytheodore/master
Re-enable static builds
2018-03-16 23:12:06 -07:00
Tony Theodore
4b2705fb32 Require Qt5Network in pkg-config file 2018-03-15 19:24:38 +11:00
Tony Theodore
b85aa4b246 Enable static builds
If unspecified, `add_library` uses the value from cmake's builtin `BUILD_SHARED_LIBS` (default true).
2018-03-15 18:47:38 +11:00
Tony Theodore
d05bdd3ec2 Use QT_STATIC to define QHTTPENGINE_EXPORT 2018-03-15 18:45:08 +11:00
Nathan Osman
e01c0f23c0 Bump patch version. 2017-11-29 18:03:00 -08:00
Nathan Osman
ada6c53130 Correct typo in package names. 2017-11-06 22:21:32 -08:00
Nathan Osman
c06432caef Update test environment to Qt 5.9.2. 2017-11-06 22:17:21 -08:00
Nathan Osman
f56b19513b Remove unnecessary code from chat example. 2017-10-25 20:06:27 -07:00
Nathan Osman
c2b21cdca4 Ensure readChannelFinished() is only emitted once (fixes #21). 2017-10-25 19:31:54 -07:00
Nathan Osman
9652e8f508 Remove unnecessary line. 2017-09-07 15:33:06 -07:00
Nathan Osman
8b3fc48108 Bump copyright. 2017-09-07 14:12:42 -07:00
Nathan Osman
83f8113a05 Fix typo. 2017-09-07 13:40:48 -07:00
Nathan Osman
65c30c9017 Fix typo. 2017-08-28 19:50:28 -07:00
Nathan Osman
a30530b21a Attempt to avoid unusual race condition. 2017-08-28 19:47:06 -07:00
Nathan Osman
40345c81b2 Fix issues with building CPack installers. 2017-08-28 17:09:18 -07:00
Nathan Osman
5fcfb11e73 Remove erroneous socket close() calls and clarify documentation. 2017-08-08 17:18:07 -07:00
Nathan Osman
0a7b81d4ba Ensure Socket::close() deletes the object once closed. 2017-08-08 17:01:05 -07:00
Nathan Osman
461d5100dc Rename internal class QProxySocket and add slot for downstream disconnect. 2017-08-08 12:18:55 -07:00
Nathan Osman
96a714c909 Fix failing server test. 2017-08-08 12:13:43 -07:00
Nathan Osman
7e962c8ac4 Ensure socket is not prematurely destroyed when client disconnects (fixes #15). 2017-08-08 12:01:39 -07:00
Nathan Osman
255200ab15 Minor fix-ups. 2017-08-08 10:38:28 -07:00
Nathan Osman
c769cee616 Remove build instructions from README in favor of linking to Doxygen documentation. 2017-08-05 11:39:24 -07:00
Nathan Osman
ad01f31cf2 Fix auth example. 2017-08-05 11:35:57 -07:00
Nathan Osman
c0c7d88366 Add server auth example. 2017-08-05 06:23:06 -07:00
Nathan Osman
25ea46861f Attempt to fix race condition that was causing the macOS build to fail. 2017-07-29 19:06:53 -07:00
Nathan Osman
77fb7c4d71 Minor fixes to proxy class. 2017-07-29 19:00:07 -07:00
Nathan Osman
76a9bc171b Ensure auth file is removed during destruction. 2017-07-22 19:06:24 -07:00
Nathan Osman
2c48893ec7 Fix target name when building on Windows. 2017-07-08 13:02:28 -07:00
Nathan Osman
da7eb850e4 Fix typos. 2017-07-08 13:00:42 -07:00
Nathan Osman
784e220276 Fix pkgconfig file. 2017-07-08 12:27:06 -07:00
Nathan Osman
2df0aa0709 Update documentation to reflect new class names. 2017-07-08 12:26:23 -07:00
Nathan Osman
5c57fda4a8 Rename export header. 2017-07-08 10:17:05 -07:00
Nathan Osman
bd7740bbc5 Update Doxyfile and documentation. 2017-07-08 10:09:49 -07:00
Nathan Osman
a77badb827 Fix failing test for QObjectHandler. 2017-07-07 22:35:25 -07:00
Nathan Osman
91ca9c82f7 Rename tests to conform with class names. 2017-07-07 22:29:08 -07:00
Nathan Osman
61d219721e Move everything into the QHttpEngine namespace. 2017-07-07 21:56:09 -07:00
Nathan Osman
6fea4fbd61 Update build scripts. 2017-07-07 21:22:19 -07:00
Nathan Osman
4866f85380 Move include and src files. 2017-07-07 21:02:01 -07:00
Nathan Osman
396168a0dd Update comment block and #include lines. 2017-07-07 20:57:57 -07:00
Nathan Osman
fd7e7e9603 Rename classes to match filenames. 2017-07-07 20:44:43 -07:00
Nathan Osman
2fdf8fac71 Rename source files. 2017-07-07 18:49:40 -07:00
Nathan Osman
e68c0b0f11 Rename most classes to remove the 'Q' prefix. 2017-07-07 18:29:50 -07:00
Nathan Osman
0197444a83 Remove all uppercase headers. 2017-07-07 16:53:03 -07:00
Nathan Osman
aa6650f6df Escape arguments for copy_if_different. 2017-05-16 11:43:06 -07:00
Nathan Osman
82a5043649 Fix chat example (fixes #16). 2017-04-04 17:34:23 -07:00
Nathan Osman
8e13f1644a Fix compilation on earlier versions of MSVC++ (fixes #17). 2017-04-03 11:16:55 -07:00
Nathan Osman
eca6097858 Removed extra newline. 2017-03-25 19:37:40 -07:00
Nathan Osman
88169f590e Minor adjustment. 2017-03-25 17:48:35 -07:00
Nathan Osman
ac6249445b Fix compilation on Qt 5.8 due to removal of QtPrivate::is_same. 2017-02-23 14:27:52 -08:00
Nathan Osman
d73fbe09aa Fix path to Qt 5.8 binaries for Travis-CI. 2017-02-23 13:58:23 -08:00
Nathan Osman
018989c081 Bump qtbase package version for Travis-CI. 2017-02-23 13:57:03 -08:00
Nathan Osman
998416abae Bump Qt version for Travis-CI. 2017-02-23 13:55:22 -08:00
Nathan Osman
a9d1891f9a Fix QLocalFile file permission error on Windows (fixes nitroshare/nitroshare-desktop#107). 2017-02-11 12:54:02 -08:00
Nathan Osman
1972d82809 Fix issue with upgraded WebSocket connections. 2016-12-13 22:21:03 -08:00
Nathan Osman
bead7a68e7 Fix incorrect HTTP version. 2016-12-13 21:31:01 -08:00
Nathan Osman
c48f20c264 Fixed logic error preventing 502 error. 2016-12-13 20:55:29 -08:00
Nathan Osman
3c662ab005 Move socket proxy code to separate class. 2016-12-13 19:44:42 -08:00
Nathan Osman
58c5dd786c Add X-Forwarded-For and X-Real-IP headers to QProxyHandler. 2016-12-13 12:17:20 -08:00
Nathan Osman
bde806b289 Add peerAddress() method to QHttpSocket. 2016-12-13 12:14:48 -08:00
Nathan Osman
48967c398b Add test for QProxyHandler. 2016-12-13 00:26:42 -08:00
Nathan Osman
ab5282c976 Add initial implementation of QProxyHandler. 2016-12-12 22:50:57 -08:00
Nathan Osman
4a2fc923f0 Merge pull request #14 from impegoraro/hotfix-socketclosed
Fixes issue #12, closes socket when done
2016-11-29 13:21:33 -08:00
Ilan Pegoraro
927d6436d1 Fixes issue #12, closes socket when done
Closing the socket after calling the invokeSlot and not in QHttpServer
after process, because the invocation can be deferred if more data
needs to be read from the socket, so closing the socket after calling
invokeSlot guarantees that the socket is close when the slot finishes.
2016-11-03 12:23:24 +00:00
Nathan Osman
651fb87314 Regenerate private key and cert because OS X was choking on them for some stupid reason. 2016-10-29 01:16:22 -07:00
Nathan Osman
0c22530acc Add test for SSL. 2016-10-28 22:15:14 -07:00
Nathan Osman
8ca3666112 Add missing call to setSocketDescriptor(). 2016-10-28 14:52:46 -07:00
Nathan Osman
49aa771b63 Add SSL capabilities to QHttpServer. 2016-10-28 13:44:16 -07:00
Nathan Osman
1633a085c7 Merge pull request #11 from impegoraro/master
Add more http status codes
2016-10-28 10:35:23 -07:00
Ilan Pegoraro
79da42f0b9 Add more http status code 2016-10-28 16:51:37 +01:00
Nathan Osman
f454b0be7e Update documentation home page. 2016-10-17 22:34:34 -07:00
Nathan Osman
c3fc9fd400 Prevent multiple enum values from appearing on one line. 2016-10-15 22:26:01 -07:00
Nathan Osman
cf3932529a Add QLocalAuth class. 2016-10-15 20:50:11 -07:00
Nathan Osman
482a67733f Finish moving JSON code into QHttpSocket. 2016-10-14 16:11:29 -07:00
Nathan Osman
f059e9bc66 Move JSON output functionality to socket. 2016-10-14 14:14:06 -07:00
Nathan Osman
4dd17ad4ea Fix bug with include paths on MSVC. 2016-10-14 11:02:19 -07:00
Nathan Osman
7e7e6945a0 Fix MSVC template error. 2016-10-14 00:36:00 -07:00
Nathan Osman
4cfbec7c6c Rename QHttpSocket::QQueryStringMap and QHttpSocket::QHttpHeaderMap. 2016-10-13 17:34:46 -07:00
Nathan Osman
5968739f03 Add JSON utility functions to QObjectHandler. 2016-10-13 15:33:55 -07:00
Nathan Osman
c1fc8a115d Remove all JSON marshalling code from QObjectHandler. 2016-10-13 00:38:39 -07:00
Nathan Osman
e8b076bdc4 Update documentation. 2016-10-12 12:07:59 -07:00
Nathan Osman
1604bcf279 Fix runtime bug caused by typo. 2016-10-11 22:33:00 -07:00
Nathan Osman
6edf9f4c89 Add HTTP basic authentication. 2016-10-11 21:29:06 -07:00
Nathan Osman
6df88ad4aa Implement middleware. 2016-10-11 20:23:21 -07:00
Nathan Osman
e35fdbde78 Fix comparison issue with QIByteArray and improve performance by inlining all methods. 2016-10-11 13:07:09 -07:00
Nathan Osman
1c1c1e7752 Change default path of example to current directory. 2016-10-10 22:01:14 -07:00
Nathan Osman
642ea69ffc Update README to reflect new requirements for building. 2016-10-10 21:32:21 -07:00
Nathan Osman
8304e03c6d Minor formatting and documentation updates. 2016-10-10 13:49:34 -07:00
Nathan Osman
0708516e28 Fix path to header. 2016-10-10 13:25:26 -07:00
Nathan Osman
17e8b4e8aa Merge pull request #10 from Kuraisu/range-support 2016-10-10 13:21:14 -07:00
Aleksey Yermakov
7e7ec8c7af Add partial content support to QFilesystemHandler. 2016-10-10 18:00:17 +03:00
Aleksey Yermakov
f7290e0cb9 Added QHttpRange class and tests.
This class will be used for support of partial content requests.
2016-10-09 22:43:29 +03:00
Nathan Osman
a700e854b8 Fix compilation issues on MSVC2013. 2016-10-08 16:08:00 -07:00
Nathan Osman
7ac8fa1c88 Merge branch 'new-features' 2016-10-08 13:00:00 -07:00
Nathan Osman
7c3a13e89d Bump version. 2016-10-08 12:48:24 -07:00
Nathan Osman
409ba4be75 Update documentation for QObjectHandler. 2016-10-08 12:45:29 -07:00
Nathan Osman
27cc231218 Fix typo causing failing test. 2016-10-08 12:32:17 -07:00
Nathan Osman
f73a7659f1 Add tests for new-style connection syntax. 2016-10-08 12:28:57 -07:00
Nathan Osman
bf4ca27cc7 Update tests for QObjecHandler. 2016-10-08 01:47:30 -07:00
Nathan Osman
b901757731 Completely rewrite QObjectHandler to be more flexible. 2016-10-07 23:02:16 -07:00
Nathan Osman
1bea4efab7 Merge pull request #9 from impegoraro/hotfix-httphandler
Close the socket on default process method
2016-10-07 23:01:10 -07:00
Ilan Pegoraro
00fab6b5e1 Close the socket on default process method
Just like in QObjectHandler after writing to the socket, the socket
should be close.
2016-10-07 17:58:54 +01:00
Nathan Osman
d20c1d33a9 Fix incorrect parameter for QHttpParser test. 2016-10-06 22:58:13 -07:00
Nathan Osman
337d021936 Fix path issue with CMake. 2016-10-06 22:47:51 -07:00
Nathan Osman
9dd1072bc3 Update .travis.yml for Qt 5.7. 2016-10-06 22:21:11 -07:00
Nathan Osman
7c460edb57 Completely reorganize the library, moving up to Qt 5.4, using C++11 where reasonably possible, and reorganizing the includes. 2016-10-06 21:50:45 -07:00
Nathan Osman
7930ef4a50 Fix inadvertently inverted condition during status line parsing. 2016-10-06 10:58:49 -07:00
Nathan Osman
c6cb5d274b Switch to using QMultiMap for headers and added new typedef for query string. 2016-10-05 00:34:14 -07:00
Nathan Osman
b39b18be6c Add ability to modify status code. 2016-10-04 19:51:48 -07:00
Nathan Osman
287edf9825 Update documentation. 2016-10-04 17:27:53 -07:00
Nathan Osman
61bfd8c70a Update chat example to use new slot format. 2016-10-04 17:23:40 -07:00
Nathan Osman
feaab366ef Fix SEGFAULT caused by parameter going out of scope. 2016-10-04 17:02:35 -07:00
Nathan Osman
7edb26c433 Add ability to specify method for slot handlers. 2016-10-04 16:48:57 -07:00
Nathan Osman
391dea5ddb Merge commit 'f1ab4f4' into http-methods 2016-10-04 11:27:52 -07:00
Nathan Osman
dafff09b5d Fixed OS definition. 2016-07-11 10:41:17 -07:00
Nathan Osman
7ea1f703db Fixed access control issue on Windows. 2016-06-19 04:39:14 -07:00
Nathan Osman
e83dd36bfd Switched qInfo to qDebug. 2016-06-19 04:24:52 -07:00
Nathan Osman
7cea9352f3 Added CPack information to enable quick TGZ generation. 2016-06-18 16:24:59 -07:00
Nathan Osman
56c37275cd Merge branch 'master' of github.com:nitroshare/qhttpengine 2016-06-13 19:11:34 -07:00
Nathan Osman
27ed1bbe4a Updated CI badge in README. 2016-05-30 10:11:33 -07:00
Nathan Osman
30099e78ea Added language specification and builds for both GCC and Clang. 2016-05-30 10:08:17 -07:00
Nathan Osman
3cf92f2373 Added .travis.yml. 2016-05-30 10:03:03 -07:00
Nathan Osman
7554193a16 Added client portion of auth sample. 2016-05-28 12:17:07 -07:00
Nathan Osman
dd748698a3 Fixed issue with chmod() on Windows. 2016-05-28 07:31:28 -07:00
Ilan Pegoraro
f1ab4f40b9 Add support for GET methods.
Add support for HTTP GET method.
Mapping between the http GET and POST method is done by prefixing the
the keyword to the correct slot. For example, given a GET request /example,
the slot should be named get_example, so that proper routing can be
made.

Also, the GET slot receives as argument the query string as a
QVariantMap.
2016-01-07 11:20:35 +00:00
102 changed files with 5182 additions and 1883 deletions

21
.travis.yml Normal file
View File

@@ -0,0 +1,21 @@
sudo: required
dist: trusty
language: c++
compiler:
- clang
- gcc
install:
- sudo add-apt-repository -y ppa:beineri/opt-qt592-trusty
- sudo apt-get update -qq
- sudo apt-get install -qq cmake qt59base
before_script:
- export PATH=$PATH:/opt/qt59/bin
script:
- cmake -DBUILD_TESTS=on .
- make
- ctest

View File

@@ -1,49 +1,59 @@
cmake_minimum_required(VERSION 2.8.11)
project(QHttpEngine)
if(NOT (CMAKE_MAJOR_VERSION VERSION_LESS 3))
cmake_policy(SET CMP0043 OLD)
endif()
cmake_minimum_required(VERSION 3.2.0)
project(qhttpengine)
set(PROJECT_NAME "QHttpEngine")
set(PROJECT_DESCRIPTION "Simple and secure HTTP server for Qt applications")
set(PROJECT_AUTHOR "Nathan Osman")
set(PROJECT_URL "https://github.com/nitroshare/qhttpengine")
set(PROJECT_VERSION_MAJOR 0)
set(PROJECT_VERSION_MINOR 1)
set(PROJECT_VERSION_PATCH 0)
set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
set(PROJECT_VERSION_MAJOR 1)
set(PROJECT_VERSION_MINOR 0)
set(PROJECT_VERSION_PATCH 1)
set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH})
option(BUILD_STATIC "Build a static library" OFF)
option(BUILD_DOC "Build Doxygen documentation" OFF)
option(BUILD_EXAMPLES "Build the example applications" OFF)
option(BUILD_TESTS "Build the test suite" OFF)
# Build a shared library by default
option(BUILD_SHARED_LIBS "Build QHttpEngine as a shared library" ON)
set(BIN_INSTALL_DIR bin CACHE STRING "Binary runtime installation directory relative to the install prefix")
set(LIB_INSTALL_DIR lib CACHE STRING "Library installation directory relative to the install prefix")
set(INCLUDE_INSTALL_DIR include CACHE STRING "Header installation directory relative to the install prefix")
set(CMAKECONFIG_INSTALL_DIR "${LIB_INSTALL_DIR}/cmake/${PROJECT_NAME}" CACHE STRING "CMake configuration installation directory")
set(DOC_INSTALL_DIR share/doc/${PROJECT_NAME} CACHE STRING "Documentation installation directory relative to the install prefix")
set(EXAMPLES_INSTALL_DIR "${LIB_INSTALL_DIR}/${PROJECT_NAME}/examples" CACHE STRING "Examples installation directory relative to the install prefix")
find_package(Qt5Network 5.1 REQUIRED)
set(DOC_INSTALL_DIR share/doc/qhttpengine CACHE STRING "Documentation installation directory relative to the install prefix")
set(EXAMPLES_INSTALL_DIR "${LIB_INSTALL_DIR}/qhttpengine/examples" CACHE STRING "Examples installation directory relative to the install prefix")
find_package(Qt5Network 5.4 REQUIRED)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
add_subdirectory(src)
option(BUILD_DOC "Build Doxygen documentation" OFF)
if(BUILD_DOC)
find_package(Doxygen REQUIRED)
add_subdirectory(doc)
endif()
option(BUILD_EXAMPLES "Build the example applications" OFF)
if(BUILD_EXAMPLES)
add_subdirectory(examples)
endif()
option(BUILD_TESTS "Build the test suite" OFF)
if(BUILD_TESTS)
find_package(Qt5Test 5.4 REQUIRED)
enable_testing()
add_subdirectory(tests)
endif()
set(CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}")
set(CPACK_PACKAGE_VENDOR "${PROJECT_AUTHOR}")
set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
set(CPACK_COMPONENT_DOCUMENTATION_DISPLAY_NAME "Documentation")
set(CPACK_COMPONENT_DOCUMENTATION_DESCRIPTION "Documentation generated for the library")
set(CPACK_COMPONENT_EXAMPLES_DISPLAY_NAME "Examples")
set(CPACK_COMPONENT_EXAMPLES_DESCRIPTION "Sample applications using the library")
include(CPack)

View File

@@ -1,40 +1,12 @@
## QHTTPEngine
[![Build Status](https://snap-ci.com/nitroshare/qhttpengine/branch/master/build_image)](https://snap-ci.com/nitroshare/qhttpengine/branch/master)
[![Build Status](https://travis-ci.org/nitroshare/qhttpengine.svg?branch=master)](https://travis-ci.org/nitroshare/qhttpengine)
[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://opensource.org/licenses/MIT)
Simple set of classes for developing HTTP server applications in Qt.
### Build Requirements
### Documentation
QHttpEngine requires a modern C++ compiler supported by the Qt framework. Some examples include:
To learn more about building and using the library, please visit this page:
- Microsoft Visual C++ Express
- GCC (including MinGW-w64)
- Clang
CMake 2.8.11+ and Qt 5.1+ are required to build the library.
### Build Instructions
Use the instructions below to build the library:
1. Open a terminal or command prompt and run the following commands to create a directory for the files that will be built:
mkdir build
cd build
2. Run CMake to generate the Makefile that will be used to build the library:
cmake ..
**Note:** on Windows, you will need to change the last command to the following in order to generate a Makefile:
cmake -G "NMake Makefiles" ..
3. Build the library:
- **Unix-based (including MinGW-w64):**
`make`
- **Windows (MSVC++):**
`nmake`
https://ci.quickmediasolutions.com/job/qhttpengine-documentation/doxygen/

View File

@@ -1,5 +1,3 @@
find_package(Doxygen REQUIRED)
configure_file(Doxyfile.in "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile")
add_custom_target(doc ALL
@@ -8,4 +6,5 @@ add_custom_target(doc ALL
install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/html"
DESTINATION "${DOC_INSTALL_DIR}"
COMPONENT documentation
)

View File

@@ -1,4 +1,4 @@
# Doxyfile 1.8.8
# Doxyfile 1.8.13
# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project.
@@ -46,10 +46,10 @@ PROJECT_NUMBER = "@PROJECT_VERSION@"
PROJECT_BRIEF = "@PROJECT_DESCRIPTION@"
# With the PROJECT_LOGO tag one can specify an logo or icon that is included in
# the documentation. The maximum height of the logo should not exceed 55 pixels
# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo
# to the output directory.
# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
# in the documentation. The maximum height of the logo should not exceed 55
# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
# the logo to the output directory.
PROJECT_LOGO =
@@ -60,7 +60,7 @@ PROJECT_LOGO =
OUTPUT_DIRECTORY =
# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub-
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
# directories (in 2 levels) under the output directory of each output format and
# will distribute the generated files over these directories. Enabling this
# option can be useful when feeding doxygen a huge amount of source files, where
@@ -93,14 +93,14 @@ ALLOW_UNICODE_NAMES = NO
OUTPUT_LANGUAGE = English
# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member
# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
# descriptions after the members that are listed in the file and class
# documentation (similar to Javadoc). Set to NO to disable this.
# The default value is: YES.
BRIEF_MEMBER_DESC = YES
# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief
# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
# description of a member or function before the detailed description
#
# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
@@ -118,7 +118,17 @@ REPEAT_BRIEF = NO
# the entity):The $name class, The $name widget, The $name file, is, provides,
# specifies, contains, represents, a, an and the.
ABBREVIATE_BRIEF =
ABBREVIATE_BRIEF = "The $name class" \
"The $name widget" \
"The $name file" \
is \
provides \
specifies \
contains \
represents \
a \
an \
the
# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
# doxygen will generate a detailed section even if there is only a brief
@@ -135,7 +145,7 @@ ALWAYS_DETAILED_SEC = NO
INLINE_INHERITED_MEMB = NO
# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path
# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
# before files name in the file list and in the header files. If set to NO the
# shortest path that makes the file name unique will be used
# The default value is: YES.
@@ -161,7 +171,7 @@ STRIP_FROM_PATH =
# specify the list of include paths that are normally passed to the compiler
# using the -I flag.
STRIP_FROM_INC_PATH =
STRIP_FROM_INC_PATH = "@CMAKE_SOURCE_DIR@/src/include"
# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
# less readable) file names. This can be useful is your file systems doesn't
@@ -205,9 +215,9 @@ MULTILINE_CPP_IS_BRIEF = NO
INHERIT_DOCS = YES
# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a
# new page for each member. If set to NO, the documentation of a member will be
# part of the file/class/namespace that contains it.
# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
# page for each member. If set to NO, the documentation of a member will be part
# of the file/class/namespace that contains it.
# The default value is: NO.
SEPARATE_MEMBER_PAGES = NO
@@ -276,7 +286,7 @@ OPTIMIZE_OUTPUT_VHDL = NO
# instance to make doxygen treat .inc files as Fortran files (default is PHP),
# and .f files as C (default is Fortran), use: inc=Fortran f=C.
#
# Note For files without extension you can use no_extension as a placeholder.
# Note: For files without extension you can use no_extension as a placeholder.
#
# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
# the files are not read by doxygen.
@@ -293,10 +303,19 @@ EXTENSION_MAPPING =
MARKDOWN_SUPPORT = YES
# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
# to that level are automatically included in the table of contents, even if
# they do not have an id attribute.
# Note: This feature currently applies only to Markdown headings.
# Minimum value: 0, maximum value: 99, default value: 0.
# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
TOC_INCLUDE_HEADINGS = 0
# When enabled doxygen tries to link words that correspond to documented
# classes, or namespaces to their corresponding documentation. Such a link can
# be prevented in individual cases by by putting a % sign in front of the word
# or globally by setting AUTOLINK_SUPPORT to NO.
# be prevented in individual cases by putting a % sign in front of the word or
# globally by setting AUTOLINK_SUPPORT to NO.
# The default value is: YES.
AUTOLINK_SUPPORT = YES
@@ -336,13 +355,20 @@ SIP_SUPPORT = NO
IDL_PROPERTY_SUPPORT = YES
# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
# tag is set to YES, then doxygen will reuse the documentation of the first
# tag is set to YES then doxygen will reuse the documentation of the first
# member in the group (if any) for the other members of the group. By default
# all members of a group must be documented explicitly.
# The default value is: NO.
DISTRIBUTE_GROUP_DOC = NO
# If one adds a struct or class to a group and this option is enabled, then also
# any nested class or struct is added to the same group. By default this option
# is disabled and one has to add nested compounds explicitly via \ingroup.
# The default value is: NO.
GROUP_NESTED_COMPOUNDS = NO
# Set the SUBGROUPING tag to YES to allow class member groups of the same type
# (for instance a group of public functions) to be put as a subgroup of that
# type (e.g. under the Public Functions section). Set it to NO to prevent
@@ -401,7 +427,7 @@ LOOKUP_CACHE_SIZE = 0
# Build related configuration options
#---------------------------------------------------------------------------
# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
# documentation are documented, even if no documentation was available. Private
# class members and static file members will be hidden unless the
# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
@@ -411,35 +437,35 @@ LOOKUP_CACHE_SIZE = 0
EXTRACT_ALL = NO
# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will
# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
# be included in the documentation.
# The default value is: NO.
EXTRACT_PRIVATE = NO
# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal
# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
# scope will be included in the documentation.
# The default value is: NO.
EXTRACT_PACKAGE = NO
# If the EXTRACT_STATIC tag is set to YES all static members of a file will be
# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
# included in the documentation.
# The default value is: NO.
EXTRACT_STATIC = NO
# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined
# locally in source files will be included in the documentation. If set to NO
# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
# locally in source files will be included in the documentation. If set to NO,
# only classes defined in header files are included. Does not have any effect
# for Java sources.
# The default value is: YES.
EXTRACT_LOCAL_CLASSES = YES
# This flag is only useful for Objective-C code. When set to YES local methods,
# This flag is only useful for Objective-C code. If set to YES, local methods,
# which are defined in the implementation section but not in the interface are
# included in the documentation. If set to NO only methods in the interface are
# included in the documentation. If set to NO, only methods in the interface are
# included.
# The default value is: NO.
@@ -464,21 +490,21 @@ HIDE_UNDOC_MEMBERS = NO
# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
# undocumented classes that are normally visible in the class hierarchy. If set
# to NO these classes will be included in the various overviews. This option has
# no effect if EXTRACT_ALL is enabled.
# to NO, these classes will be included in the various overviews. This option
# has no effect if EXTRACT_ALL is enabled.
# The default value is: NO.
HIDE_UNDOC_CLASSES = NO
# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
# (class|struct|union) declarations. If set to NO these declarations will be
# (class|struct|union) declarations. If set to NO, these declarations will be
# included in the documentation.
# The default value is: NO.
HIDE_FRIEND_COMPOUNDS = NO
# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
# documentation blocks found inside the body of a function. If set to NO these
# documentation blocks found inside the body of a function. If set to NO, these
# blocks will be appended to the function's detailed documentation block.
# The default value is: NO.
@@ -492,7 +518,7 @@ HIDE_IN_BODY_DOCS = NO
INTERNAL_DOCS = NO
# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
# names in lower-case letters. If set to YES upper-case letters are also
# names in lower-case letters. If set to YES, upper-case letters are also
# allowed. This is useful if you have classes or files whose names only differ
# in case and if your file system supports case sensitive file names. Windows
# and Mac users are advised to set this option to NO.
@@ -501,12 +527,19 @@ INTERNAL_DOCS = NO
CASE_SENSE_NAMES = YES
# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
# their full class and namespace scopes in the documentation. If set to YES the
# their full class and namespace scopes in the documentation. If set to YES, the
# scope will be hidden.
# The default value is: NO.
HIDE_SCOPE_NAMES = NO
# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
# append additional text to a page's title, such as Class Reference. If set to
# YES the compound reference will be hidden.
# The default value is: NO.
HIDE_COMPOUND_REFERENCE= NO
# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
# the files that are included by a file in the documentation of that file.
# The default value is: YES.
@@ -534,14 +567,14 @@ INLINE_INFO = YES
# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
# (detailed) documentation of file and class members alphabetically by member
# name. If set to NO the members will appear in declaration order.
# name. If set to NO, the members will appear in declaration order.
# The default value is: YES.
SORT_MEMBER_DOCS = YES
# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
# descriptions of file, namespace and class members alphabetically by member
# name. If set to NO the members will appear in declaration order. Note that
# name. If set to NO, the members will appear in declaration order. Note that
# this will also influence the order of the classes in the class list.
# The default value is: NO.
@@ -586,27 +619,25 @@ SORT_BY_SCOPE_NAME = NO
STRICT_PROTO_MATCHING = NO
# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the
# todo list. This list is created by putting \todo commands in the
# documentation.
# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
# list. This list is created by putting \todo commands in the documentation.
# The default value is: YES.
GENERATE_TODOLIST = YES
# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the
# test list. This list is created by putting \test commands in the
# documentation.
# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
# list. This list is created by putting \test commands in the documentation.
# The default value is: YES.
GENERATE_TESTLIST = YES
# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug
# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
# list. This list is created by putting \bug commands in the documentation.
# The default value is: YES.
GENERATE_BUGLIST = YES
# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO)
# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
# the deprecated list. This list is created by putting \deprecated commands in
# the documentation.
# The default value is: YES.
@@ -631,8 +662,8 @@ ENABLED_SECTIONS =
MAX_INITIALIZER_LINES = 30
# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
# the bottom of the documentation of classes and structs. If set to YES the list
# will mention the files that were used to generate the documentation.
# the bottom of the documentation of classes and structs. If set to YES, the
# list will mention the files that were used to generate the documentation.
# The default value is: YES.
SHOW_USED_FILES = YES
@@ -696,7 +727,7 @@ CITE_BIB_FILES =
QUIET = YES
# The WARNINGS tag can be used to turn on/off the warning messages that are
# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES
# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
# this implies that the warnings are on.
#
# Tip: Turn warnings on while writing the documentation.
@@ -704,7 +735,7 @@ QUIET = YES
WARNINGS = YES
# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate
# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
# will automatically be disabled.
# The default value is: YES.
@@ -721,12 +752,18 @@ WARN_IF_DOC_ERROR = YES
# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
# are documented, but have no documentation for their parameters or return
# value. If set to NO doxygen will only warn about wrong or incomplete parameter
# documentation, but not about the absence of documentation.
# value. If set to NO, doxygen will only warn about wrong or incomplete
# parameter documentation, but not about the absence of documentation.
# The default value is: NO.
WARN_NO_PARAMDOC = NO
# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
# a warning is encountered.
# The default value is: NO.
WARN_AS_ERROR = NO
# The WARN_FORMAT tag determines the format of the warning messages that doxygen
# can produce. The string should contain the $file, $line, and $text tags, which
# will be replaced by the file and line number from which the warning originated
@@ -750,10 +787,10 @@ WARN_LOGFILE =
# The INPUT tag is used to specify the files and/or directories that contain
# documented source files. You may enter file names like myfile.cpp or
# directories like /usr/src/myproject. Separate the files or directories with
# spaces.
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
INPUT = "@CMAKE_CURRENT_SOURCE_DIR@/index.md" "@CMAKE_SOURCE_DIR@/src/@PROJECT_NAME@"
INPUT = "@CMAKE_CURRENT_SOURCE_DIR@/index.md" "@CMAKE_SOURCE_DIR@/src/include/qhttpengine"
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
@@ -766,14 +803,62 @@ INPUT_ENCODING = UTF-8
# If the value of the INPUT tag contains directories, you can use the
# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
# *.h) to filter out the source-files in the directories. If left blank the
# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii,
# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp,
# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown,
# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf,
# *.qsf, *.as and *.js.
# *.h) to filter out the source-files in the directories.
#
# Note that for custom extensions or not directly supported extensions you also
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
# read by doxygen.
#
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf.
FILE_PATTERNS = *.h
FILE_PATTERNS = *.c \
*.cc \
*.cxx \
*.cpp \
*.c++ \
*.java \
*.ii \
*.ixx \
*.ipp \
*.i++ \
*.inl \
*.idl \
*.ddl \
*.odl \
*.h \
*.hh \
*.hxx \
*.hpp \
*.h++ \
*.cs \
*.d \
*.php \
*.php4 \
*.php5 \
*.phtml \
*.inc \
*.m \
*.markdown \
*.md \
*.mm \
*.dox \
*.py \
*.pyw \
*.f90 \
*.f95 \
*.f03 \
*.f08 \
*.f \
*.for \
*.tcl \
*.vhd \
*.vhdl \
*.ucf \
*.qsf
# The RECURSIVE tag can be used to specify whether or not subdirectories should
# be searched for input files as well.
@@ -804,7 +889,7 @@ EXCLUDE_SYMLINKS = NO
# Note that the wildcards are matched against the file with absolute path, so to
# exclude all test directories for example use the pattern */test/*
EXCLUDE_PATTERNS = *_p.h
EXCLUDE_PATTERNS =
# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
# (namespaces, classes, functions, etc.) that should be excluded from the
@@ -815,7 +900,7 @@ EXCLUDE_PATTERNS = *_p.h
# Note that the wildcards are matched against the file with absolute path, so to
# exclude all test directories use the pattern */test/*
EXCLUDE_SYMBOLS = *Private
EXCLUDE_SYMBOLS =
# The EXAMPLE_PATH tag can be used to specify one or more files or directories
# that contain example code fragments that are included (see the \include
@@ -828,7 +913,7 @@ EXAMPLE_PATH =
# *.h) to filter out the source-files in the directories. If left blank all
# files are included.
EXAMPLE_PATTERNS =
EXAMPLE_PATTERNS = *
# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
# searched for input files to be used with the \include or \dontinclude commands
@@ -857,6 +942,10 @@ IMAGE_PATH =
# Note that the filter must not add or remove lines; it is applied before the
# code is scanned, but not when the output code is generated. If lines are added
# or removed, the anchors will not be placed correctly.
#
# Note that for custom extensions or not directly supported extensions you also
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
# properly processed by doxygen.
INPUT_FILTER =
@@ -866,11 +955,15 @@ INPUT_FILTER =
# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
# patterns match the file name, INPUT_FILTER is applied.
#
# Note that for custom extensions or not directly supported extensions you also
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
# properly processed by doxygen.
FILTER_PATTERNS =
# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
# INPUT_FILTER ) will also be used to filter the input files that are used for
# INPUT_FILTER) will also be used to filter the input files that are used for
# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
# The default value is: NO.
@@ -930,7 +1023,7 @@ REFERENCED_BY_RELATION = NO
REFERENCES_RELATION = NO
# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
# to YES, then the hyperlinks from functions in REFERENCES_RELATION and
# to YES then the hyperlinks from functions in REFERENCES_RELATION and
# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
# link to the documentation.
# The default value is: YES.
@@ -977,13 +1070,13 @@ USE_HTAGS = NO
VERBATIM_HEADERS = YES
# If the CLANG_ASSISTED_PARSING tag is set to YES, then doxygen will use the
# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the
# cost of reduced performance. This can be particularly helpful with template
# rich C++ code for which doxygen's built-in parser lacks the necessary type
# information.
# Note: The availability of this option depends on whether or not doxygen was
# compiled with the --with-libclang option.
# generated with the -Duse-libclang=ON option for CMake.
# The default value is: NO.
CLANG_ASSISTED_PARSING = NO
@@ -1026,7 +1119,7 @@ IGNORE_PREFIX =
# Configuration options related to the HTML output
#---------------------------------------------------------------------------
# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output
# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
# The default value is: YES.
GENERATE_HTML = YES
@@ -1092,10 +1185,10 @@ HTML_STYLESHEET =
# cascading style sheets that are included after the standard style sheets
# created by doxygen. Using this option one can overrule certain style aspects.
# This is preferred over using HTML_STYLESHEET since it does not replace the
# standard style sheet and is therefor more robust against future updates.
# standard style sheet and is therefore more robust against future updates.
# Doxygen will copy the style sheet files to the output directory.
# Note: The order of the extra stylesheet files is of importance (e.g. the last
# stylesheet in the list overrules the setting of the previous ones in the
# Note: The order of the extra style sheet files is of importance (e.g. the last
# style sheet in the list overrules the setting of the previous ones in the
# list). For an example see the documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.
@@ -1112,7 +1205,7 @@ HTML_EXTRA_STYLESHEET = "@CMAKE_CURRENT_SOURCE_DIR@/overrides.css"
HTML_EXTRA_FILES =
# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
# will adjust the colors in the stylesheet and background images according to
# will adjust the colors in the style sheet and background images according to
# this color. Hue is specified as an angle on a colorwheel, see
# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
@@ -1143,11 +1236,12 @@ HTML_COLORSTYLE_GAMMA = 80
# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
# page will contain the date and time when the page was generated. Setting this
# to NO can help when comparing the output of multiple runs.
# The default value is: YES.
# to YES can help to show when doxygen was last run and thus if the
# documentation is up to date.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_TIMESTAMP = YES
HTML_TIMESTAMP = NO
# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
# documentation will contain sections that can be hidden and shown after the
@@ -1240,28 +1334,28 @@ GENERATE_HTMLHELP = NO
CHM_FILE =
# The HHC_LOCATION tag can be used to specify the location (absolute path
# including file name) of the HTML help compiler ( hhc.exe). If non-empty
# including file name) of the HTML help compiler (hhc.exe). If non-empty,
# doxygen will try to run the HTML help compiler on the generated index.hhp.
# The file has to be specified with full path.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
HHC_LOCATION =
# The GENERATE_CHI flag controls if a separate .chi index file is generated (
# YES) or that it should be included in the master .chm file ( NO).
# The GENERATE_CHI flag controls if a separate .chi index file is generated
# (YES) or that it should be included in the master .chm file (NO).
# The default value is: NO.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
GENERATE_CHI = NO
# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc)
# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
# and project file content.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
CHM_INDEX_ENCODING =
# The BINARY_TOC flag controls whether a binary table of contents is generated (
# YES) or a normal table of contents ( NO) in the .chm file. Furthermore it
# The BINARY_TOC flag controls whether a binary table of contents is generated
# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
# enables the Previous and Next buttons.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
@@ -1375,7 +1469,7 @@ DISABLE_INDEX = NO
# index structure (just like the one that is generated for HTML Help). For this
# to work a browser that supports JavaScript, DHTML, CSS and frames is required
# (i.e. any modern browser). Windows users are probably better off using the
# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can
# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
# further fine-tune the look of the index. As an example, the default style
# sheet generated by doxygen has an example that shows how to put an image at
# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
@@ -1394,7 +1488,7 @@ GENERATE_TREEVIEW = NO
# Minimum value: 0, maximum value: 20, default value: 4.
# This tag requires that the tag GENERATE_HTML is set to YES.
ENUM_VALUES_PER_LINE = 4
ENUM_VALUES_PER_LINE = 1
# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
# to set the initial width (in pixels) of the frame in which the tree is shown.
@@ -1403,7 +1497,7 @@ ENUM_VALUES_PER_LINE = 4
TREEVIEW_WIDTH = 250
# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to
# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
# external symbols imported via tag files in a separate window.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
@@ -1432,7 +1526,7 @@ FORMULA_TRANSPARENT = YES
# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
# http://www.mathjax.org) which uses client side Javascript for the rendering
# instead of using prerendered bitmaps. Use this if you do not have LaTeX
# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
# installed or if you want to formulas look prettier in the HTML output. When
# enabled you may also need to install MathJax separately and configure the path
# to it using the MATHJAX_RELPATH option.
@@ -1518,7 +1612,7 @@ SERVER_BASED_SEARCH = NO
# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
# search results.
#
# Doxygen ships with an example indexer ( doxyindexer) and search engine
# Doxygen ships with an example indexer (doxyindexer) and search engine
# (doxysearch.cgi) which are based on the open source search engine library
# Xapian (see: http://xapian.org/).
#
@@ -1531,7 +1625,7 @@ EXTERNAL_SEARCH = NO
# The SEARCHENGINE_URL should point to a search engine hosted by a web server
# which will return the search results when EXTERNAL_SEARCH is enabled.
#
# Doxygen ships with an example indexer ( doxyindexer) and search engine
# Doxygen ships with an example indexer (doxyindexer) and search engine
# (doxysearch.cgi) which are based on the open source search engine library
# Xapian (see: http://xapian.org/). See the section "External Indexing and
# Searching" for details.
@@ -1569,7 +1663,7 @@ EXTRA_SEARCH_MAPPINGS =
# Configuration options related to the LaTeX output
#---------------------------------------------------------------------------
# If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output.
# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
# The default value is: YES.
GENERATE_LATEX = NO
@@ -1600,7 +1694,7 @@ LATEX_CMD_NAME = latex
MAKEINDEX_CMD_NAME = makeindex
# If the COMPACT_LATEX tag is set to YES doxygen generates more compact LaTeX
# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
# documents. This may be useful for small projects and may help to save some
# trees in general.
# The default value is: NO.
@@ -1618,9 +1712,12 @@ COMPACT_LATEX = NO
PAPER_TYPE = a4
# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
# that should be included in the LaTeX output. To get the times font for
# instance you can specify
# EXTRA_PACKAGES=times
# that should be included in the LaTeX output. The package can be specified just
# by its name or with the correct syntax as to be used with the LaTeX
# \usepackage command. To get the times font for instance you can specify :
# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
# To use the option intlimits with the amsmath package you can specify:
# EXTRA_PACKAGES=[intlimits]{amsmath}
# If left blank no extra packages will be included.
# This tag requires that the tag GENERATE_LATEX is set to YES.
@@ -1635,9 +1732,9 @@ EXTRA_PACKAGES =
# Note: Only use a user-defined header if you know what you are doing! The
# following commands have a special meaning inside the header: $title,
# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
# $projectbrief, $projectlogo. Doxygen will replace $title with the empy string,
# for the replacement values of the other commands the user is refered to
# HTML_HEADER.
# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
# string, for the replacement values of the other commands the user is referred
# to HTML_HEADER.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_HEADER =
@@ -1653,6 +1750,17 @@ LATEX_HEADER =
LATEX_FOOTER =
# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
# LaTeX style sheets that are included after the standard style sheets created
# by doxygen. Using this option one can overrule certain style aspects. Doxygen
# will copy the style sheet files to the output directory.
# Note: The order of the extra style sheet files is of importance (e.g. the last
# style sheet in the list overrules the setting of the previous ones in the
# list).
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_EXTRA_STYLESHEET =
# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
# other source files which should be copied to the LATEX_OUTPUT output
# directory. Note that the files will be copied as-is; there are no commands or
@@ -1671,7 +1779,7 @@ LATEX_EXTRA_FILES =
PDF_HYPERLINKS = YES
# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
# the PDF file directly from the LaTeX files. Set this option to YES to get a
# the PDF file directly from the LaTeX files. Set this option to YES, to get a
# higher quality PDF documentation.
# The default value is: YES.
# This tag requires that the tag GENERATE_LATEX is set to YES.
@@ -1712,11 +1820,19 @@ LATEX_SOURCE_CODE = NO
LATEX_BIB_STYLE = plain
# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
# page will contain the date and time when the page was generated. Setting this
# to NO can help when comparing the output of multiple runs.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_TIMESTAMP = NO
#---------------------------------------------------------------------------
# Configuration options related to the RTF output
#---------------------------------------------------------------------------
# If the GENERATE_RTF tag is set to YES doxygen will generate RTF output. The
# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
# RTF output is optimized for Word 97 and may not look too pretty with other RTF
# readers/editors.
# The default value is: NO.
@@ -1731,7 +1847,7 @@ GENERATE_RTF = NO
RTF_OUTPUT = rtf
# If the COMPACT_RTF tag is set to YES doxygen generates more compact RTF
# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
# documents. This may be useful for small projects and may help to save some
# trees in general.
# The default value is: NO.
@@ -1768,11 +1884,21 @@ RTF_STYLESHEET_FILE =
RTF_EXTENSIONS_FILE =
# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
# with syntax highlighting in the RTF output.
#
# Note that which sources are shown also depends on other settings such as
# SOURCE_BROWSER.
# The default value is: NO.
# This tag requires that the tag GENERATE_RTF is set to YES.
RTF_SOURCE_CODE = NO
#---------------------------------------------------------------------------
# Configuration options related to the man page output
#---------------------------------------------------------------------------
# If the GENERATE_MAN tag is set to YES doxygen will generate man pages for
# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
# classes and files.
# The default value is: NO.
@@ -1816,7 +1942,7 @@ MAN_LINKS = NO
# Configuration options related to the XML output
#---------------------------------------------------------------------------
# If the GENERATE_XML tag is set to YES doxygen will generate an XML file that
# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
# captures the structure of the code including all documentation.
# The default value is: NO.
@@ -1830,7 +1956,7 @@ GENERATE_XML = NO
XML_OUTPUT = xml
# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program
# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
# listings (including syntax highlighting and cross-referencing information) to
# the XML output. Note that enabling this will significantly increase the size
# of the XML output.
@@ -1843,7 +1969,7 @@ XML_PROGRAMLISTING = YES
# Configuration options related to the DOCBOOK output
#---------------------------------------------------------------------------
# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files
# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
# that can be used to generate PDF.
# The default value is: NO.
@@ -1857,7 +1983,7 @@ GENERATE_DOCBOOK = NO
DOCBOOK_OUTPUT = docbook
# If the DOCBOOK_PROGRAMLISTING tag is set to YES doxygen will include the
# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
# program listings (including syntax highlighting and cross-referencing
# information) to the DOCBOOK output. Note that enabling this will significantly
# increase the size of the DOCBOOK output.
@@ -1870,10 +1996,10 @@ DOCBOOK_PROGRAMLISTING = NO
# Configuration options for the AutoGen Definitions output
#---------------------------------------------------------------------------
# If the GENERATE_AUTOGEN_DEF tag is set to YES doxygen will generate an AutoGen
# Definitions (see http://autogen.sf.net) file that captures the structure of
# the code including all documentation. Note that this feature is still
# experimental and incomplete at the moment.
# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
# AutoGen Definitions (see http://autogen.sf.net) file that captures the
# structure of the code including all documentation. Note that this feature is
# still experimental and incomplete at the moment.
# The default value is: NO.
GENERATE_AUTOGEN_DEF = NO
@@ -1882,7 +2008,7 @@ GENERATE_AUTOGEN_DEF = NO
# Configuration options related to the Perl module output
#---------------------------------------------------------------------------
# If the GENERATE_PERLMOD tag is set to YES doxygen will generate a Perl module
# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
# file that captures the structure of the code including all documentation.
#
# Note that this feature is still experimental and incomplete at the moment.
@@ -1890,7 +2016,7 @@ GENERATE_AUTOGEN_DEF = NO
GENERATE_PERLMOD = NO
# If the PERLMOD_LATEX tag is set to YES doxygen will generate the necessary
# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
# output from the Perl module output.
# The default value is: NO.
@@ -1898,9 +2024,9 @@ GENERATE_PERLMOD = NO
PERLMOD_LATEX = NO
# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be nicely
# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
# formatted so it can be parsed by a human reader. This is useful if you want to
# understand what is going on. On the other hand, if this tag is set to NO the
# understand what is going on. On the other hand, if this tag is set to NO, the
# size of the Perl module output will be much smaller and Perl will parse it
# just the same.
# The default value is: YES.
@@ -1920,14 +2046,14 @@ PERLMOD_MAKEVAR_PREFIX =
# Configuration options related to the preprocessor
#---------------------------------------------------------------------------
# If the ENABLE_PREPROCESSING tag is set to YES doxygen will evaluate all
# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
# C-preprocessor directives found in the sources and include files.
# The default value is: YES.
ENABLE_PREPROCESSING = YES
# If the MACRO_EXPANSION tag is set to YES doxygen will expand all macro names
# in the source code. If set to NO only conditional compilation will be
# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
# in the source code. If set to NO, only conditional compilation will be
# performed. Macro expansion can be done in a controlled way by setting
# EXPAND_ONLY_PREDEF to YES.
# The default value is: NO.
@@ -1943,7 +2069,7 @@ MACRO_EXPANSION = NO
EXPAND_ONLY_PREDEF = NO
# If the SEARCH_INCLUDES tag is set to YES the includes files in the
# If the SEARCH_INCLUDES tag is set to YES, the include files in the
# INCLUDE_PATH will be searched if a #include is found.
# The default value is: YES.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
@@ -1973,7 +2099,7 @@ INCLUDE_FILE_PATTERNS =
# recursively expanded use the := operator instead of the = operator.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
PREDEFINED =
PREDEFINED = DOXYGEN
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
# tag can be used to specify a list of macro names that should be expanded. The
@@ -2019,20 +2145,21 @@ TAGFILES =
GENERATE_TAGFILE =
# If the ALLEXTERNALS tag is set to YES all external class will be listed in the
# class index. If set to NO only the inherited external classes will be listed.
# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
# the class index. If set to NO, only the inherited external classes will be
# listed.
# The default value is: NO.
ALLEXTERNALS = NO
# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed in
# the modules index. If set to NO, only the current project's groups will be
# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
# in the modules index. If set to NO, only the current project's groups will be
# listed.
# The default value is: YES.
EXTERNAL_GROUPS = YES
# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in
# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
# the related pages index. If set to NO, only the current project's pages will
# be listed.
# The default value is: YES.
@@ -2049,7 +2176,7 @@ PERL_PATH = /usr/bin/perl
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
# If the CLASS_DIAGRAMS tag is set to YES doxygen will generate a class diagram
# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
# NO turns the diagrams off. Note that this option also works with HAVE_DOT
# disabled, but it is recommended to install and use dot, since it yields more
@@ -2074,7 +2201,7 @@ MSCGEN_PATH =
DIA_PATH =
# If set to YES, the inheritance and collaboration graphs will hide inheritance
# If set to YES the inheritance and collaboration graphs will hide inheritance
# and usage relations if the target is undocumented or is not a class.
# The default value is: YES.
@@ -2147,7 +2274,7 @@ COLLABORATION_GRAPH = YES
GROUP_GRAPHS = YES
# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
# collaboration diagrams in a style similar to the OMG's Unified Modeling
# Language.
# The default value is: NO.
@@ -2199,7 +2326,8 @@ INCLUDED_BY_GRAPH = YES
#
# Note that enabling this option will significantly increase the time of a run.
# So in most cases it will be better to enable call graphs for selected
# functions only using the \callgraph command.
# functions only using the \callgraph command. Disabling a call graph can be
# accomplished by means of the command \hidecallgraph.
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.
@@ -2210,7 +2338,8 @@ CALL_GRAPH = NO
#
# Note that enabling this option will significantly increase the time of a run.
# So in most cases it will be better to enable caller graphs for selected
# functions only using the \callergraph command.
# functions only using the \callergraph command. Disabling a caller graph can be
# accomplished by means of the command \hidecallergraph.
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.
@@ -2233,13 +2362,17 @@ GRAPHICAL_HIERARCHY = YES
DIRECTORY_GRAPH = YES
# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
# generated by dot.
# generated by dot. For an explanation of the image formats see the section
# output formats in the documentation of the dot tool (Graphviz (see:
# http://www.graphviz.org/)).
# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
# to make the SVG files visible in IE 9+ (other browsers do not have this
# requirement).
# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,
# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,
# gif:cairo:gd, gif:gd, gif:gd:gd and svg.
# gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd, png:cairo,
# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
# png:gdiplus:gdiplus.
# The default value is: png.
# This tag requires that the tag HAVE_DOT is set to YES.
@@ -2287,10 +2420,19 @@ DIAFILE_DIRS =
# PlantUML is not used or called during a preprocessing step. Doxygen will
# generate a warning when it encounters a \startuml command in this case and
# will not generate output for the diagram.
# This tag requires that the tag HAVE_DOT is set to YES.
PLANTUML_JAR_PATH =
# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
# configuration file for plantuml.
PLANTUML_CFG_FILE =
# When using plantuml, the specified paths are searched for files specified by
# the !include statement in a plantuml block.
PLANTUML_INCLUDE_PATH =
# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
# that will be shown in the graph. If the number of nodes in a graph becomes
# larger than this value, doxygen will truncate the graph, which is visualized
@@ -2327,7 +2469,7 @@ MAX_DOT_GRAPH_DEPTH = 0
DOT_TRANSPARENT = NO
# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
# files in one run (i.e. multiple -o and -T options on the command line). This
# makes dot run faster, but since only newer versions of dot (>1.8.10) support
# this, this feature is disabled by default.
@@ -2344,7 +2486,7 @@ DOT_MULTI_TARGETS = NO
GENERATE_LEGEND = YES
# If the DOT_CLEANUP tag is set to YES doxygen will remove the intermediate dot
# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
# files that are used to generate the various graphs.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.

View File

@@ -9,46 +9,113 @@ The design goals of QHttpEngine include:
All of QHttpEngine's functionality is included in a single monolithic library.
## Requirements
## Build Requirements
QHttpEngine has been tested on the following combinations of compiler and operating system:
- Visual C++ 2013 on 32 and 64-bit editions of Windows
- Clang on Mac OS X
- Visual C++ 2013 on Windows 7, 10, and Server 2012 R2
- g++ and Clang on Mac OS X
- g++ on i386, amd64, and ARM builds of Linux
QHttpEngine is designed in a portable way, so it may run on other compilers and operating systems than the ones listed above. However, the list represents the combinations that are actively tested and officially supported.
## Building
## Build Instructions
QHttpEngine uses CMake for building the library. The library recognizes four options during configuration, all of which are disabled by default (the library is built as a shared library):
QHttpEngine uses CMake for building the library. The library recognizes three options during configuration, all of which are disabled by default (the library is built as a shared library):
- `BUILD_STATIC` - build and link a static library instead of a shared library
- `BUILD_DOC` - (requires Doxygen) generates documentation from the comments in the source code
- `BUILD_EXAMPLES` - builds the sample applications that demonstrate how to use QHttpEngine
- `BUILD_TESTS` - build the test suite
It is also possible to override installation directories by customizing the `BIN_INSTALL_DIR`, `LIB_INSTALL_DIR`, `INCLUDE_INSTALL_DIR`, `CMAKECONFIG_INSTALL_DIR`, `DOC_INSTALL_DIR`, and `EXAMPLES_INSTALL_DIR` variables.
It is also possible to override installation directories by customizing the `BIN_INSTALL_DIR`, `LIB_INSTALL_DIR`, `INCLUDE_INSTALL_DIR`, `DOC_INSTALL_DIR`, and `EXAMPLES_INSTALL_DIR` variables.
## Usage
## Basic Usage
Serving static files from a directory is as simple as creating an instance of QHttpServer and QFilesystemHandler:
QHttpEngine includes all of the classes you will need to build your HTTP server application.
### Socket
In order to create an HTTP socket, create an instance of [Socket](@ref QHttpEngine::Socket) and pass a QTcpSocket* in the constructor:
@code
QFilesystemHandler handler("/var/www");
QHttpServer server(&handler);
QTcpSocket *tcpSocket = ...
QHttpEngine::Socket httpSocket(tcpSocket);
@endcode
Once the [headersParsed()](@ref QHttpEngine::Socket::headersParsed) signal is emitted (and [isHeadersParsed()](@ref QHttpEngine::Socket::isHeadersParsed) returns true), information about the request can easily be retrieved:
@code
// Check if the method is GET
bool isGet = httpSocket.method() == QHttpEngine::Socket::GET;
// Retrieve the path
QString path = httpSocket.path();
// Lookup the value of the "User-Agent" header
QByteArray userAgent = httpSocket.headers().value("User-Agent");
@endcode
Because [Socket](@ref QHttpEngine::Socket) derives from QIODevice, writing a response to the client is very simple:
@code
httpSocket.setStatusCode(QHttpEngine::Socket::OK);
httpSocket.setHeader("Content-Type", "text/plain");
httpSocket.writeHeaders();
httpSocket.write("This is a sample message.");
@endcode
Writing a local file to the socket can be done with little effort by using the [QIODeviceCopier](@ref QHttpEngine::QIODeviceCopier) class:
@code
QFile file("somefile.txt");
file.open(QIODevice::ReadOnly);
QHttpEngine::QIODeviceCopier copier(&file, &httpSocket);
copier.start();
// Wait for the finished() signal from copier
@endcode
### Server
To create an HTTP server, simply create an instance of the [Server](@ref QHttpEngine::Server) class:
@code
QHttpEngine::Server server;
server.listen();
@endcode
QHttpEngine can also be used to easily add an HTTP API to an existing application by deriving a class from QObjectHandler. Each signal that takes a single QVariantMap parameter and returns a QVariantMap can be directly invoked by using its name in the request path. For example:
In order to route requests based on their path, a handler must be used. Handlers derive from the [Handler](@ref QHttpEngine::Handler) class. The simplest of these is the [FilesystemHandler](@ref QHttpEngine::FilesystemHandler) class:
@code
class ApiHandler : public QObjectHandler
QHttpEngine::FilesystemHandler handler("/var/www");
server.setHandler(&handler);
@endcode
A request to `/path` will cause the server to respond with the contents of `/var/www/path`.
### Slot Methods
Although it is possible to create a handler that manually routes requests, it is far easier to use the [QObjectHandler](@ref QHttpEngine::QObjectHandler) class and register slots for each path - you can even use the new connection syntax:
@code
class Api : public QObject
{
Q_OBJECT
public slots:
QVariantMap doSomething(const QVariantMap &);
void doSomething(QHttpEngine::Socket *socket);
void doSomethingElse(QHttpEngine::Socket *socket);
};
Api api;
QHttpEngine::QObjectHandler handler;
handler.registerMethod("something", &api, &Api::doSomething);
@endcode
A client can send a POST request to `/doSomething` with JSON data and receive the response from the slot directly as JSON data. The chatserver example provides a demonstration of this.
A request to `/something` will cause the `doSomething()` slot to be invoked.
## Where to Go From Here
- Middleware can be used to process requests before final routing: [Middleware](@ref QHttpEngine::Middleware)
- Authentication middleware can be used to restrict access: [BasicAuthMiddleware](@ref QHttpEngine::BasicAuthMiddleware), [LocalAuthMiddleware](@ref QHttpEngine::LocalAuthMiddleware)

View File

@@ -1,45 +1,34 @@
/**
* Overrides for Doxygen CSS
* Copyright 2015 - Nathan Osman
*/
/* Ensure that a sane font is selected by default since
the current stylesheet uses Roboto if available */
body, table, div, p, dl {
font-family: Helvetica, Arial, sans-serif;
}
/* Remove the underline from the titlearea and increase the margin */
#titlearea {
border-bottom: 0;
margin-bottom: 16px;
margin: 10px 0;
}
/* The .footer class is also used in the img at the bottom for some reason */
.footer img {
width: auto;
#projectalign {
padding-left: 0 !important;
}
/* Add the border back to the tab list */
.tabs {
border-top: 1px solid #5373b4;
.fragment {
padding: 4px !important;
}
/* The next two rules fix up the search box */
.tabs .tablist li:last-child {
float: right;
position: relative;
}
.tablist {
width: 100%;
}
/* Ensure that certain parts of the page are never wider than about 900px */
@media (min-width: 992px) {
#top, .header, .contents, .footer {
margin-left: auto !important;
margin-right: auto !important;
width: 800px;
margin: auto !important;
max-width: 900px;
}
#titlearea {
border-bottom: none;
}
#main-nav, #navrow1 {
border-radius: 4px 4px 0 0;
border-top: 1px solid #c4cfe5;
position: relative;
}
#main-nav, #navrow1, .header, .nav-path, .contents {
border-left: 1px solid #c4cfe5;
border-right: 1px solid #c4cfe5;
box-sizing: border-box;
}
.contents {
padding: 14px;
}
hr.footer {
border-top: 1px solid #c4cfe5;
}
}

View File

@@ -1,4 +1,5 @@
set(EXAMPLES
auth
chatserver
fileserver
)
@@ -8,5 +9,6 @@ foreach(EXAMPLE ${EXAMPLES})
add_subdirectory(${EXAMPLE})
install(DIRECTORY ${EXAMPLE}
DESTINATION "${EXAMPLES_INSTALL_DIR}"
COMPONENT examples
)
endforeach()

View File

@@ -0,0 +1,21 @@
# Client
add_executable(authclient client.cpp)
target_link_libraries(authclient qhttpengine)
set_target_properties(authclient PROPERTIES
CXX_STANDARD 11
)
install(TARGETS authclient
RUNTIME DESTINATION "${EXAMPLE_DIR}"
COMPONENT examples
)
# Server
add_executable(authserver server.cpp)
target_link_libraries(authserver qhttpengine)
set_target_properties(authserver PROPERTIES
CXX_STANDARD 11
)
install(TARGETS authserver
RUNTIME DESTINATION "${EXAMPLE_DIR}"
COMPONENT examples
)

76
examples/auth/client.cpp Normal file
View File

@@ -0,0 +1,76 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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.
*/
#include <QCoreApplication>
#include <QDir>
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrl>
int main(int argc, char * argv[])
{
QCoreApplication a(argc, argv);
// Attempt to open the local file and read from it
QFile file(QDir::home().absoluteFilePath(".authserver"));
if (!file.open(QIODevice::ReadOnly)) {
qCritical("Unable to open local file - is server running?");
return 1;
}
// Parse the JSON to get the port and token
QJsonObject obj = QJsonDocument::fromJson(file.readAll()).object();
if (!obj.contains("port") || !obj.contains("token")) {
qCritical("Malformed JSON in local file.");
return 1;
}
// Close the file
file.close();
// Create a request to the server, using the provided port and passing the
// auth token as a custom HTTP header
QUrl url(QString("http://127.0.0.1:%1/").arg(obj.value("port").toInt()));
QNetworkRequest request(url);
request.setRawHeader("X-Auth-Token", obj.value("token").toString().toUtf8());
// Send the request
QNetworkAccessManager manager;
QNetworkReply *reply = manager.get(request);
// Check the response
QObject::connect(reply, &QNetworkReply::finished, [&a, reply]() {
if (reply->error() == QNetworkReply::NoError) {
qDebug("Successfully authenticated to server.");
a.exit();
} else {
qCritical("Error: %s", reply->errorString().toUtf8().constData());
a.exit(1);
}
});
return a.exec();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -20,42 +20,38 @@
* IN THE SOFTWARE.
*/
#include <QObject>
#include <QTest>
#include <QCoreApplication>
#include <QHostAddress>
#include <QVariantMap>
#include <QHttpEngine/QIByteArray>
#include <qhttpengine/handler.h>
#include <qhttpengine/localauthmiddleware.h>
#include <qhttpengine/qobjecthandler.h>
#include <qhttpengine/server.h>
#include <qhttpengine/socket.h>
const char *Value1 = "test";
const char *Value2 = "TEST";
class TestQIByteArray : public QObject
int main(int argc, char * argv[])
{
Q_OBJECT
QCoreApplication a(argc, argv);
private Q_SLOTS:
QHttpEngine::QObjectHandler handler;
handler.registerMethod("", [](QHttpEngine::Socket *socket) {
socket->setStatusCode(QHttpEngine::Socket::OK);
socket->writeHeaders();
socket->close();
});
void testQString();
void testQByteArray();
void testCharPtr();
};
QHttpEngine::Server server(&handler);
if (!server.listen(QHostAddress::LocalHost)) {
qCritical("unable to bind to a local port");
a.exit(1);
}
void TestQIByteArray::testQString()
{
QVERIFY(QIByteArray(Value1) == QString(Value2));
QVERIFY(QString(Value1) == QIByteArray(Value2));
QHttpEngine::LocalAuthMiddleware middleware;
middleware.setData(QVariantMap{
{"port", server.serverPort()},
});
handler.addMiddleware(&middleware);
return a.exec();
}
void TestQIByteArray::testQByteArray()
{
QVERIFY(QIByteArray(Value1) == QByteArray(Value2));
QVERIFY(QByteArray(Value1) == QIByteArray(Value2));
}
void TestQIByteArray::testCharPtr()
{
QVERIFY(QIByteArray(Value1) == Value2);
QVERIFY(Value1 == QIByteArray(Value2));
}
QTEST_MAIN(TestQIByteArray)
#include "TestQIByteArray.moc"

View File

@@ -6,8 +6,9 @@ set(SRC
qt5_add_resources(QRC resources.qrc)
add_executable(chatserver ${SRC} ${QRC})
target_link_libraries(chatserver QHttpEngine)
target_link_libraries(chatserver qhttpengine)
install(TARGETS chatserver
RUNTIME DESTINATION "${EXAMPLE_DIR}"
COMPONENT examples
)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -20,44 +20,31 @@
* IN THE SOFTWARE.
*/
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QVariantMap>
#include "apihandler.h"
QVariantMap ApiHandler::postMessage(const QVariantMap &params)
void ApiHandler::messages(QHttpEngine::Socket *socket)
{
// Ensure that a valid message was supplied
if(!params.contains("message")) {
return QVariantMap();
}
// Add the new message to the list
mMessages.append(params.value("message").toString());
return QVariantMap();
QJsonObject object;
object.insert("messages", QJsonArray::fromStringList(mMessages));
socket->writeJson(QJsonDocument(object));
}
QVariantMap ApiHandler::getMessages(const QVariantMap &params)
void ApiHandler::messagesNew(QHttpEngine::Socket *socket)
{
// Ensure an index was supplied
if(!params.contains("index")) {
return QVariantMap();
}
int index = params.value("index").toInt();
QVariantList messages;
// Construct a list of all messages with an index higher than the one
// that was provided as a parameter
if(index >= -1 && index < mMessages.count()) {
for(QStringList::const_iterator i = mMessages.constBegin() + index + 1;
i != mMessages.constEnd(); ++i) {
QVariantMap data;
data.insert("index", i - mMessages.constBegin());
data.insert("message", *i);
messages.append(data);
QJsonDocument document;
if (socket->readJson(document)) {
QVariantMap data = document.object().toVariantMap();
if (data.contains("message")) {
mMessages.append(data.value("message").toString());
socket->writeHeaders();
socket->close();
return;
}
}
// Return the list of messages
QVariantMap data;
data.insert("messages", messages);
return data;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -23,18 +23,19 @@
#ifndef CHAT_APIHANDLER_H
#define CHAT_APIHANDLER_H
#include <QObject>
#include <QStringList>
#include <QHttpEngine/QObjectHandler>
#include <qhttpengine/socket.h>
class ApiHandler : public QObjectHandler
class ApiHandler : public QObject
{
Q_OBJECT
public Q_SLOTS:
QVariantMap postMessage(const QVariantMap &params);
QVariantMap getMessages(const QVariantMap &params);
void messages(QHttpEngine::Socket *socket);
void messagesNew(QHttpEngine::Socket *socket);
private:

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -27,9 +27,10 @@
#include <QRegExp>
#include <QStringList>
#include <QHttpEngine/QFilesystemHandler>
#include <QHttpEngine/QHttpHandler>
#include <QHttpEngine/QHttpServer>
#include <qhttpengine/filesystemhandler.h>
#include <qhttpengine/handler.h>
#include <qhttpengine/server.h>
#include <qhttpengine/qobjecthandler.h>
#include "apihandler.h"
@@ -63,16 +64,19 @@ int main(int argc, char * argv[])
quint16 port = parser.value(portOption).toInt();
// Build the hierarchy of handlers
QFilesystemHandler handler(":/static");
QHttpEngine::FilesystemHandler handler(":/static");
handler.addRedirect(QRegExp("^$"), "/index.html");
ApiHandler apiHandler;
ApiHandler renameMe;
QHttpEngine::QObjectHandler apiHandler;
apiHandler.registerMethod("messages", &renameMe, &ApiHandler::messages);
apiHandler.registerMethod("messages/new", &renameMe, &ApiHandler::messagesNew);
handler.addSubHandler(QRegExp("api/"), &apiHandler);
QHttpServer server(&handler);
QHttpEngine::Server server(&handler);
// Attempt to listen on the specified port
if(!server.listen(address, port)) {
if (!server.listen(address, port)) {
qCritical("Unable to listen on the specified port.");
return 1;
}

View File

@@ -32,7 +32,7 @@ body {
padding: 4px;
}
.messages {
.container {
padding: 10px;
}

View File

@@ -13,10 +13,12 @@
<textarea id="input" placeholder="Type here and press enter..."></textarea>
</div>
<div class="messages">
<div class="container">
<div class="message system">
Greetings! Welcome to the QHttpEngine chat demo. Please type a message using the entry box below.
</div>
<div class="messages">
</div>
</div>
<div class="spacer"></div>

View File

@@ -1,26 +1,23 @@
$(function() {
var index = -1,
$document = $(document),
var $document = $(document),
$messages = $('.messages');
// Retrieve all messages after the specified index
function update() {
$.ajax({
type: 'POST',
url: '/api/getMessages',
data: JSON.stringify({index: index}),
url: '/api/messages',
contentType: 'application/json',
complete: function() {
window.setTimeout(update, 2000);
},
success: function(data) {
$.each(data.messages, function(i, e) {
$messages.empty();
$.each(data.messages, function() {
$('<div>')
.addClass('message')
.text(e.message)
.text(this)
.appendTo($messages);
index = e.index;
});
$document.scrollTop(10000);
}
@@ -31,10 +28,10 @@ $(function() {
// Find the message input box and set the appropriate handler for [enter]
var $input = $('#input').focus().keypress(function(e) {
if(e.which == 13) {
if(e.which === 13) {
$.ajax({
type: 'POST',
url: '/api/postMessage',
url: '/api/messages/new',
data: JSON.stringify({message: $(this).val()}),
contentType: 'application/json'
});

View File

@@ -3,8 +3,9 @@ set(SRC
)
add_executable(fileserver ${SRC})
target_link_libraries(fileserver QHttpEngine)
target_link_libraries(fileserver qhttpengine)
install(TARGETS fileserver
RUNTIME DESTINATION "${EXAMPLE_DIR}"
COMPONENT examples
)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -27,8 +27,8 @@
#include <QHostAddress>
#include <QStringList>
#include <QHttpEngine/QFilesystemHandler>
#include <QHttpEngine/QHttpServer>
#include <qhttpengine/filesystemhandler.h>
#include <qhttpengine/server.h>
int main(int argc, char * argv[])
{
@@ -54,7 +54,7 @@ int main(int argc, char * argv[])
QStringList() << "d" << "directory",
"directory to serve",
"directory",
QDir::homePath()
QDir::currentPath()
);
parser.addOption(dirOption);
parser.addHelpOption();
@@ -68,11 +68,11 @@ int main(int argc, char * argv[])
QString dir = parser.value(dirOption);
// Create the filesystem handler and server
QFilesystemHandler handler(dir);
QHttpServer server(&handler);
QHttpEngine::FilesystemHandler handler(dir);
QHttpEngine::Server server(&handler);
// Attempt to listen on the specified port
if(!server.listen(address, port)) {
if (!server.listen(address, port)) {
qCritical("Unable to listen on the specified port.");
return 1;
}

View File

@@ -1,18 +1,37 @@
file(GLOB HEADERS "${PROJECT_NAME}/*")
configure_file(qhttpengine_export.h.in "${CMAKE_CURRENT_BINARY_DIR}/qhttpengine_export.h")
configure_file(qhttpengine.h.in "${CMAKE_CURRENT_BINARY_DIR}/qhttpengine.h")
set(HEADERS "${HEADERS}" "${CMAKE_CURRENT_BINARY_DIR}/qhttpengine.h")
set(HEADERS
include/qhttpengine/basicauthmiddleware.h
include/qhttpengine/filesystemhandler.h
include/qhttpengine/handler.h
include/qhttpengine/ibytearray.h
include/qhttpengine/localauthmiddleware.h
include/qhttpengine/localfile.h
include/qhttpengine/middleware.h
include/qhttpengine/parser.h
include/qhttpengine/proxyhandler.h
include/qhttpengine/qiodevicecopier.h
include/qhttpengine/qobjecthandler.h
include/qhttpengine/range.h
include/qhttpengine/server.h
include/qhttpengine/socket.h
"${CMAKE_CURRENT_BINARY_DIR}/qhttpengine_export.h"
)
set(SRC
qfilesystemhandler.cpp
qhttphandler.cpp
qhttpparser.cpp
qhttpserver.cpp
qhttpsocket.cpp
qibytearray.cpp
qiodevicecopier.cpp
qlocalfile.cpp
qobjecthandler.cpp
src/filesystemhandler.cpp
src/basicauthmiddleware.cpp
src/handler.cpp
src/parser.cpp
src/range.cpp
src/server.cpp
src/socket.cpp
src/qiodevicecopier.cpp
src/localauthmiddleware.cpp
src/localfile.cpp
src/qobjecthandler.cpp
src/proxyhandler.cpp
src/proxysocket.cpp
)
if(WIN32)
@@ -20,51 +39,50 @@ if(WIN32)
set(SRC ${SRC} "${CMAKE_CURRENT_BINARY_DIR}/resource.rc")
endif()
if(BUILD_STATIC)
add_library(QHttpEngine STATIC ${HEADERS} ${SRC})
else()
add_library(QHttpEngine SHARED ${HEADERS} ${SRC})
endif()
add_library(qhttpengine ${HEADERS} ${SRC})
qt5_use_modules(QHttpEngine Network)
set_target_properties(qhttpengine PROPERTIES
CXX_STANDARD 11
CXX_STANDARD_REQUIRED ON
DEFINE_SYMBOL QT_NO_SIGNALS_SLOTS_KEYWORDS
DEFINE_SYMBOL QHTTPENGINE_LIBRARY
PUBLIC_HEADER "${HEADERS}"
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
)
target_include_directories(QHttpEngine PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
target_include_directories(qhttpengine PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>"
"$<INSTALL_INTERFACE:${INCLUDE_INSTALL_DIR}>"
)
set_target_properties(QHttpEngine PROPERTIES
DEFINE_SYMBOL QT_NO_SIGNALS_SLOTS_KEYWORDS
DEFINE_SYMBOL QHTTPENGINE_LIBRARY
PUBLIC_HEADER "${HEADERS}"
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
)
target_link_libraries(qhttpengine Qt5::Network)
install(TARGETS QHttpEngine EXPORT QHttpEngine-export
install(TARGETS qhttpengine EXPORT qhttpengine-export
RUNTIME DESTINATION "${BIN_INSTALL_DIR}"
LIBRARY DESTINATION "${LIB_INSTALL_DIR}"
ARCHIVE DESTINATION "${LIB_INSTALL_DIR}"
PUBLIC_HEADER DESTINATION "${INCLUDE_INSTALL_DIR}/${PROJECT_NAME}"
PUBLIC_HEADER DESTINATION "${INCLUDE_INSTALL_DIR}/qhttpengine"
)
install(EXPORT QHttpEngine-export DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
FILE ${PROJECT_NAME}Config.cmake
install(EXPORT qhttpengine-export
FILE qhttpengineConfig.cmake
DESTINATION "${LIB_INSTALL_DIR}/cmake/qhttpengine"
)
include(CMakePackageConfigHelpers)
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/qhttpengineConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/qhttpengineConfigVersion.cmake"
DESTINATION "${LIB_INSTALL_DIR}/cmake/qhttpengine"
)
configure_file(${PROJECT_NAME}.pc.in "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc" @ONLY)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc"
configure_file(qhttpengine.pc.in "${CMAKE_CURRENT_BINARY_DIR}/qhttpengine.pc" @ONLY)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/qhttpengine.pc"
DESTINATION "${LIB_INSTALL_DIR}/pkgconfig"
)

View File

@@ -1 +0,0 @@
#include "qfilesystemhandler.h"

View File

@@ -1 +0,0 @@
#include "qhttphandler.h"

View File

@@ -1 +0,0 @@
#include "qhttpparser.h"

View File

@@ -1 +0,0 @@
#include "qhttpserver.h"

View File

@@ -1 +0,0 @@
#include "qhttpsocket.h"

View File

@@ -1 +0,0 @@
#include "qibytearray.h"

View File

@@ -1 +0,0 @@
#include "qiodevicecopier.h"

View File

@@ -1 +0,0 @@
#include "qlocalfile.h"

View File

@@ -1 +0,0 @@
#include "qobjecthandler.h"

View File

@@ -1,65 +0,0 @@
/*
* Copyright (c) 2015 Nathan Osman
*
* 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 QHTTPENGINE_QIBYTEARRAY_H
#define QHTTPENGINE_QIBYTEARRAY_H
#include <QByteArray>
#include "qhttpengine.h"
/**
* @brief Case-insensitive subclass of QByteArray
* @headerfile qibytearray.h QHttpEngine/QIByteArray
*
* The QIByteArray is identical to the QByteArray class in all aspects except
* that it performs comparisons in a case-insensitive manner.
*/
class QHTTPENGINE_EXPORT QIByteArray : public QByteArray
{
public:
/**
* @brief Create an empty QIByteArray
*/
QIByteArray();
/**
* @brief QIByteArray copy constructor
*/
QIByteArray(const QByteArray &other);
/**
* @brief Create a QIByteArray from a const char *
*/
QIByteArray(const char *data, int size = -1);
};
QHTTPENGINE_EXPORT bool operator==(const QIByteArray &a1, const QIByteArray &a2);
QHTTPENGINE_EXPORT bool operator==(const QIByteArray &a1, const QString &a2);
QHTTPENGINE_EXPORT bool operator==(const QString &a1, const QIByteArray &a2);
QHTTPENGINE_EXPORT bool operator==(const QIByteArray &a1, const QByteArray &a2);
QHTTPENGINE_EXPORT bool operator==(const QByteArray &a1, const QIByteArray &a2);
QHTTPENGINE_EXPORT bool operator==(const QIByteArray &a1, const char *a2);
QHTTPENGINE_EXPORT bool operator==(const char *a1, const QIByteArray &a2);
#endif // QHTTPENGINE_QIBYTEARRAY_H

View File

@@ -1,84 +0,0 @@
/*
* Copyright (c) 2015 Nathan Osman
*
* 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 QHTTPENGINE_QOBJECTHANDLER_H
#define QHTTPENGINE_QOBJECTHANDLER_H
#include "qhttpengine.h"
#include "qhttphandler.h"
class QHTTPENGINE_EXPORT QObjectHandlerPrivate;
/**
* @brief Handler for invoking slots
* @headerfile qobjecthandler.h QHttpEngine/QObjectHandler
*
* This handler enables incoming requests to invoke a matching slot in a
* QObject-derived class. The request body is expected to contain parameters
* encoded as a JSON object. This object is then passed to the slot as a
* single QVariantMap argument. The slot should return a QVariantMap
* containing the response.
*
* To use this class, it must be subclassed and one or more slots must be
* created. The name of the slot will be used to determine the path. For
* example, the following handler consists of a single method that can be
* invoked by using the `/doSomething` path.
*
* @code
* class TestHandler : public QObjectHandler
* {
* Q_OBJECT
* private slots:
* QVariantMap doSomething(QVariantMap params);
* };
* @endcode
*
* The request body must contain valid JSON which will be decoded and passed
* to the doSomething() slot as a QVariantMap. The slot should return a
* QVariantMap which will then be encoded as JSON and written to the socket as
* the response body.
*/
class QHTTPENGINE_EXPORT QObjectHandler : public QHttpHandler
{
Q_OBJECT
public:
/**
* @brief Create a new QObject handler
*/
explicit QObjectHandler(QObject *parent = 0);
protected:
/**
* @brief Reimplementation of QHttpHandler::process()
*/
virtual void process(QHttpSocket *socket, const QString &path);
private:
QObjectHandlerPrivate *const d;
friend class QObjectHandlerPrivate;
};
#endif // QHTTPENGINE_QOBJECTHANDLER_H

View File

@@ -0,0 +1,87 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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 QHTTPENGINE_BASICAUTHMIDDLEWARE_H
#define QHTTPENGINE_BASICAUTHMIDDLEWARE_H
#include <qhttpengine/middleware.h>
#include "qhttpengine_export.h"
namespace QHttpEngine
{
class QHTTPENGINE_EXPORT BasicAuthMiddlewarePrivate;
/**
* @brief %Middleware for HTTP basic authentication
*
* HTTP Basic authentication allows access to specific resources to be
* restricted. This class uses a map to store accepted username/password
* combinations, which are then used for authenticating requests. To use a
* different method of authentication, override the verify() method in a
* derived class.
*/
class QHTTPENGINE_EXPORT BasicAuthMiddleware : public Middleware
{
Q_OBJECT
public:
/**
* @brief Base constructor for the middleware
*
* The realm string is shown to a client when credentials are requested.
*/
BasicAuthMiddleware(const QString &realm, QObject *parent = Q_NULLPTR);
/**
* @brief Add credentials to the list
*
* If the username has already been added, its password will be replaced
* with the new one provided.
*/
void add(const QString &username, const QString &password);
/**
* @brief Process the request
*
* If the verify() method returns true, the client will be granted access
* to the resources. Otherwise, 401 Unauthorized will be returned.
*/
virtual bool process(Socket *socket);
protected:
/**
* @brief Determine if the client is authorized
*/
virtual bool verify(const QString &username, const QString &password);
private:
BasicAuthMiddlewarePrivate *const d;
};
}
#endif // QHTTPENGINE_BASICAUTHMIDDLEWARE_H

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -20,17 +20,20 @@
* IN THE SOFTWARE.
*/
#ifndef QHTTPENGINE_QFILESYSTEMHANDLER_H
#define QHTTPENGINE_QFILESYSTEMHANDLER_H
#ifndef QHTTPENGINE_FILESYSTEMHANDLER_H
#define QHTTPENGINE_FILESYSTEMHANDLER_H
#include "qhttpengine.h"
#include "qhttphandler.h"
#include <qhttpengine/handler.h>
class QHTTPENGINE_EXPORT QFilesystemHandlerPrivate;
#include "qhttpengine_export.h"
namespace QHttpEngine
{
class QHTTPENGINE_EXPORT FilesystemHandlerPrivate;
/**
* @brief Handler for filesystem requests
* @headerfile qfilesystemhandler.h QHttpEngine/QFilesystemHandler
* @brief %Handler for filesystem requests
*
* This handler responds to requests for resources on a local filesystem. The
* constructor is provided with a path to the root directory, which will be
@@ -38,14 +41,14 @@ class QHTTPENGINE_EXPORT QFilesystemHandlerPrivate;
* serves files from the /var/www directory:
*
* @code
* QFilesystemHandler handler("/var/www");
* QHttpEngine::FilesystemHandler handler("/var/www");
* @endcode
*
* Requests for resources outside the root will be ignored. The document root
* can be modified after initialization. It is possible to use a resource
* directory for the document root.
*/
class QHTTPENGINE_EXPORT QFilesystemHandler : public QHttpHandler
class QHTTPENGINE_EXPORT FilesystemHandler : public Handler
{
Q_OBJECT
@@ -54,12 +57,12 @@ public:
/**
* @brief Create a new filesystem handler
*/
explicit QFilesystemHandler(QObject *parent = 0);
explicit FilesystemHandler(QObject *parent = 0);
/**
* @brief Create a new filesystem handler from the specified directory
*/
QFilesystemHandler(const QString &documentRoot, QObject *parent = 0);
FilesystemHandler(const QString &documentRoot, QObject *parent = 0);
/**
* @brief Set the document root
@@ -72,14 +75,16 @@ public:
protected:
/**
* @brief Reimplementation of QHttpHandler::process()
* @brief Reimplementation of [Handler::process()](QHttpEngine::Handler::process)
*/
virtual void process(QHttpSocket *socket, const QString &path);
virtual void process(Socket *socket, const QString &path);
private:
QFilesystemHandlerPrivate *const d;
friend class QFilesystemHandlerPrivate;
FilesystemHandlerPrivate *const d;
friend class FilesystemHandlerPrivate;
};
#endif // QHTTPENGINE_QFILESYSTEMHANDLER_H
}
#endif // QHTTPENGINE_FILESYSTEMHANDLER_H

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -20,27 +20,33 @@
* IN THE SOFTWARE.
*/
#ifndef QHTTPENGINE_QHTTPHANDLER_H
#define QHTTPENGINE_QHTTPHANDLER_H
#ifndef QHTTPENGINE_HANDLER_H
#define QHTTPENGINE_HANDLER_H
#include <QObject>
#include <QRegExp>
#include "qhttpengine.h"
#include "qhttpsocket.h"
#include "qhttpengine_export.h"
class QHTTPENGINE_EXPORT QHttpHandlerPrivate;
class QRegExp;
namespace QHttpEngine
{
class Middleware;
class Socket;
class QHTTPENGINE_EXPORT HandlerPrivate;
/**
* @brief Base class for HTTP handlers
* @headerfile qhttphandler.h QHttpEngine/QHttpHandler
*
* When a request is received by a QHttpServer, it invokes the route() method
* of the root handler which is used to determine what happens to the request.
* All HTTP handlers derive from this class and should override the protected
* process() method in order to process the request. Each handler also
* maintains a list of redirects and sub-handlers which are used in place of
* invoking process() when one of the patterns match.
* When a request is received by a [Server](@ref QHttpEngine::Server), it
* invokes the route() method of the root handler which is used to determine
* what happens to the request. All HTTP handlers derive from this class and
* should override the protected process() method in order to process the
* request. Each handler also maintains a list of redirects and sub-handlers
* which are used in place of invoking process() when one of the patterns
* match.
*
* To add a redirect, use the addRedirect() method. The first parameter is a
* QRegExp pattern that the request path will be tested against. If it
@@ -48,7 +54,7 @@ class QHTTPENGINE_EXPORT QHttpHandlerPrivate;
* closed. For example, to have the root path "/" redirect to "/index.html":
*
* @code
* QHttpHandler handler;
* QHttpEngine::Handler handler;
* handler.addRedirect(QRegExp("^$"), "/index.html");
* @endcode
*
@@ -59,7 +65,7 @@ class QHTTPENGINE_EXPORT QHttpHandlerPrivate;
* invoked when the path begins with "/api/":
*
* @code
* QHttpHandler handler, subHandler;
* QHttpEngine::Handler handler, subHandler;
* handler.addSubHandler(QRegExp("^api/"), &subHandler);
* @endcode
*
@@ -68,7 +74,7 @@ class QHTTPENGINE_EXPORT QHttpHandlerPrivate;
* the request or write an error to the socket. The default implementation of
* process() simply returns an HTTP 404 error.
*/
class QHTTPENGINE_EXPORT QHttpHandler : public QObject
class QHTTPENGINE_EXPORT Handler : public QObject
{
Q_OBJECT
@@ -77,7 +83,12 @@ public:
/**
* @brief Base constructor for a handler
*/
explicit QHttpHandler(QObject *parent = 0);
explicit Handler(QObject *parent = 0);
/**
* @brief Add middleware to the handler
*/
void addMiddleware(Middleware *middleware);
/**
* @brief Add a redirect for a specific pattern
@@ -98,12 +109,12 @@ public:
* used when the route() method is invoked to determine whether the
* request matches any patterns. The order of the list is preserved.
*/
void addSubHandler(const QRegExp &pattern, QHttpHandler *handler);
void addSubHandler(const QRegExp &pattern, Handler *handler);
/**
* @brief Route an incoming request
*/
void route(QHttpSocket *socket, const QString &path);
void route(Socket *socket, const QString &path);
protected:
@@ -111,15 +122,19 @@ protected:
* @brief Process a request
*
* This method should process the request either by fulfilling it, sending
* a redirect with QHttpSocket::writeRedirect(), or writing an error to
* the socket using QHttpSocket::writeError().
* a redirect with
* [Socket::writeRedirect()](@ref QHttpEngine::Socket::writeRedirect), or
* writing an error to the socket using
* [Socket::writeError()](@ref QHttpEngine::Socket::writeError).
*/
virtual void process(QHttpSocket *socket, const QString &path);
virtual void process(Socket *socket, const QString &path);
private:
QHttpHandlerPrivate *const d;
friend class QHttpHandlerPrivate;
HandlerPrivate *const d;
friend class HandlerPrivate;
};
#endif // QHTTPENGINE_QHTTPHANDLER_H
}
#endif // QHTTPENGINE_HANDLER_H

View File

@@ -0,0 +1,102 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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 QHTTPENGINE_IBYTEARRAY_H
#define QHTTPENGINE_IBYTEARRAY_H
#include <cctype>
#include <QByteArray>
#include "qhttpengine_export.h"
namespace QHttpEngine
{
/**
* @brief Case-insensitive subclass of QByteArray
*
* The IByteArray is identical to the QByteArray class in all aspects except
* that it performs comparisons in a case-insensitive manner.
*/
class QHTTPENGINE_EXPORT IByteArray : public QByteArray
{
public:
/// \{
IByteArray() {}
IByteArray(const QByteArray &other) : QByteArray(other) {}
IByteArray(const IByteArray &other) : QByteArray(other) {}
IByteArray(const char *data, int size = -1) : QByteArray(data, size) {}
inline bool operator==(const QString &s2) const { return toLower() == s2.toLower(); }
inline bool operator!=(const QString &s2) const { return toLower() != s2.toLower(); }
inline bool operator<(const QString &s2) const { return toLower() < s2.toLower(); }
inline bool operator>(const QString &s2) const { return toLower() > s2.toLower(); }
inline bool operator<=(const QString &s2) const { return toLower() <= s2.toLower(); }
inline bool operator>=(const QString &s2) const { return toLower() >= s2.toLower(); }
bool contains(char c) const { return toLower().contains(tolower(c)); }
bool contains(const char *c) const { return toLower().contains(QByteArray(c).toLower()); }
bool contains(const QByteArray &a) const { return toLower().contains(a.toLower()); }
/// \}
};
inline bool operator==(const IByteArray &a1, const char *a2) { return a1.toLower() == QByteArray(a2).toLower(); }
inline bool operator==(const char *a1, const IByteArray &a2) { return QByteArray(a1).toLower() == a2.toLower(); }
inline bool operator==(const IByteArray &a1, const QByteArray &a2) { return a1.toLower() == a2.toLower(); }
inline bool operator==(const QByteArray &a1, const IByteArray &a2) { return a1.toLower() == a2.toLower(); }
inline bool operator==(const IByteArray &a1, const IByteArray &a2) { return a1.toLower() == a2.toLower(); }
inline bool operator!=(const IByteArray &a1, const char *a2) { return a1.toLower() != QByteArray(a2).toLower(); }
inline bool operator!=(const char *a1, const IByteArray &a2) { return QByteArray(a1).toLower() != a2.toLower(); }
inline bool operator!=(const IByteArray &a1, const QByteArray &a2) { return a1.toLower() != a2.toLower(); }
inline bool operator!=(const QByteArray &a1, const IByteArray &a2) { return a1.toLower() != a2.toLower(); }
inline bool operator!=(const IByteArray &a1, const IByteArray &a2) { return a1.toLower() != a2.toLower(); }
inline bool operator<(const IByteArray &a1, const char *a2) { return a1.toLower() < QByteArray(a2).toLower(); }
inline bool operator<(const char *a1, const IByteArray &a2) { return QByteArray(a1).toLower() < a2.toLower(); }
inline bool operator<(const IByteArray &a1, const QByteArray &a2) { return a1.toLower() < a2.toLower(); }
inline bool operator<(const QByteArray &a1, const IByteArray &a2) { return a1.toLower() < a2.toLower(); }
inline bool operator<(const IByteArray &a1, const IByteArray &a2) { return a1.toLower() < a2.toLower(); }
inline bool operator>(const IByteArray &a1, const char *a2) { return a1.toLower() > QByteArray(a2).toLower(); }
inline bool operator>(const char *a1, const IByteArray &a2) { return QByteArray(a1).toLower() > a2.toLower(); }
inline bool operator>(const IByteArray &a1, const QByteArray &a2) { return a1.toLower() > a2.toLower(); }
inline bool operator>(const QByteArray &a1, const IByteArray &a2) { return a1.toLower() > a2.toLower(); }
inline bool operator>(const IByteArray &a1, const IByteArray &a2) { return a1.toLower() > a2.toLower(); }
inline bool operator<=(const IByteArray &a1, const char *a2) { return a1.toLower() <= QByteArray(a2).toLower(); }
inline bool operator<=(const char *a1, const IByteArray &a2) { return QByteArray(a1).toLower() <= a2.toLower(); }
inline bool operator<=(const IByteArray &a1, const QByteArray &a2) { return a1.toLower() <= a2.toLower(); }
inline bool operator<=(const QByteArray &a1, const IByteArray &a2) { return a1.toLower() <= a2.toLower(); }
inline bool operator<=(const IByteArray &a1, const IByteArray &a2) { return a1.toLower() <= a2.toLower(); }
inline bool operator>=(const IByteArray &a1, const char *a2) { return a1.toLower() >= QByteArray(a2).toLower(); }
inline bool operator>=(const char *a1, const IByteArray &a2) { return QByteArray(a1).toLower() >= a2.toLower(); }
inline bool operator>=(const IByteArray &a1, const QByteArray &a2) { return a1.toLower() >= a2.toLower(); }
inline bool operator>=(const QByteArray &a1, const IByteArray &a2) { return a1.toLower() >= a2.toLower(); }
inline bool operator>=(const IByteArray &a1, const IByteArray &a2) { return a1.toLower() >= a2.toLower(); }
}
#endif // QHTTPENGINE_IBYTEARRAY_H

View File

@@ -0,0 +1,106 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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 QHTTPENGINE_LOCALAUTHMIDDLEWARE_H
#define QHTTPENGINE_LOCALAUTHMIDDLEWARE_H
#include <QVariantMap>
#include <qhttpengine/middleware.h>
#include "qhttpengine_export.h"
namespace QHttpEngine
{
class QHTTPENGINE_EXPORT LocalAuthMiddlewarePrivate;
/**
* @brief %Middleware for local file-based authentication
*
* This class is intended for authenticating applications running under the
* same user account as the server. [LocalFile](@ref QHttpEngine::LocalFile)
* is used to expose a token to connecting applications. The client passes the
* token in a special header and the request is permitted.
*
* The file consists of a JSON object in the following format:
*
* @code
* {
* "token": "{8a34d0f0-29d0-4e54-b3aa-ce8f8ad65527}"
* }
* @endcode
*
* Additional data can be added to the object using the setData() method.
*/
class QHTTPENGINE_EXPORT LocalAuthMiddleware : public Middleware
{
Q_OBJECT
public:
/**
* @brief Initialize local authentication
*
* To determine whether the local file was created successfully, call the
* exists() method.
*/
explicit LocalAuthMiddleware(QObject *parent = Q_NULLPTR);
/**
* @brief Determine whether the file exists
*/
bool exists() const;
/**
* @brief Retrieve the name of the file used for storing the token
*/
QString filename() const;
/**
* @brief Set additional data to include with the token
*/
void setData(const QVariantMap &data);
/**
* @brief Set the name of the custom header used for confirming the token
*
* The default value is "X-Auth-Token".
*/
void setHeaderName(const QByteArray &name);
/**
* @brief Process the request
*
* If the token supplied by the client matches, the request is allowed.
* Otherwise, an HTTP 403 error is returned.
*/
virtual bool process(Socket *socket);
private:
LocalAuthMiddlewarePrivate *const d;
};
}
#endif // QHTTPENGINE_LOCALAUTHMIDDLEWARE_H

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -20,26 +20,28 @@
* IN THE SOFTWARE.
*/
#ifndef QHTTPENGINE_QLOCALFILE_H
#define QHTTPENGINE_QLOCALFILE_H
#ifndef QHTTPENGINE_LOCALFILE_H
#define QHTTPENGINE_LOCALFILE_H
#include <QFile>
#include "qhttpengine.h"
#include "qhttpengine_export.h"
class QHTTPENGINE_EXPORT QLocalFilePrivate;
namespace QHttpEngine
{
class QHTTPENGINE_EXPORT LocalFilePrivate;
/**
* @brief Locally accessible file
* @headerfile qlocalfile.h QHttpEngine/QLocalFile
*
* QLocalFile uses platform-specific functions to create a file containing
* LocalFile uses platform-specific functions to create a file containing
* information that will be accessible only to the local user. This is
* typically used for storing authentication tokens:
*
* @code
* QLocalFile file;
* if(file.open()) {
* QHttpEngine::LocalFile file;
* if (file.open()) {
* file.write("private data");
* file.close();
* }
@@ -50,7 +52,7 @@ class QHTTPENGINE_EXPORT QLocalFilePrivate;
* For example, if the application name was "test" and the user's home
* directory was `/home/bob`, the absolute path would be `/home/bob/.test`.
*/
class QHTTPENGINE_EXPORT QLocalFile : public QFile
class QHTTPENGINE_EXPORT LocalFile : public QFile
{
Q_OBJECT
@@ -59,7 +61,7 @@ public:
/**
* @brief Create a new local file
*/
explicit QLocalFile(QObject *parent = 0);
explicit LocalFile(QObject *parent = 0);
/**
* @brief Attempt to open the file
@@ -72,8 +74,10 @@ public:
private:
QLocalFilePrivate *const d;
friend class QLocalFilePrivate;
LocalFilePrivate *const d;
friend class LocalFilePrivate;
};
#endif // QHTTPENGINE_QLOCALFILE_H
}
#endif // QHTTPENGINE_LOCALFILE_H

View File

@@ -0,0 +1,64 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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 QHTTPENGINE_MIDDLEWARE_H
#define QHTTPENGINE_MIDDLEWARE_H
#include <QObject>
#include "qhttpengine_export.h"
namespace QHttpEngine
{
class Socket;
/**
* @brief Pre-handler request processor
*
* Middleware sits between the server and the final request handler,
* determining whether the request should be passed on to the handler.
*/
class QHTTPENGINE_EXPORT Middleware : public QObject
{
Q_OBJECT
public:
/**
* @brief Base constructor for middleware
*/
explicit Middleware(QObject *parent = Q_NULLPTR) : QObject(parent) {}
/**
* @brief Determine if request processing should continue
*
* This method is invoked when a new request comes in. If true is
* returned, processing continues. Otherwise, it is assumed that an
* appropriate error was written to the socket.
*/
virtual bool process(Socket *socket) = 0;
};
}
#endif // QHTTPENGINE_MIDDLEWARE_H

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -20,32 +20,26 @@
* IN THE SOFTWARE.
*/
#ifndef QHTTPENGINE_QHTTPPARSER_H
#define QHTTPENGINE_QHTTPPARSER_H
#ifndef QHTTPENGINE_PARSER_H
#define QHTTPENGINE_PARSER_H
#include <QList>
#include <QMap>
#include "qhttpengine.h"
#include "qibytearray.h"
#include <qhttpengine/socket.h>
/**
* @brief Map consisting of HTTP headers
*
* The key used for the map is the QIByteArray class, which allows for
* case-insensitive comparison.
*/
typedef QMap<QIByteArray, QByteArray> QHttpHeaderMap;
#include "qhttpengine_export.h"
namespace QHttpEngine
{
/**
* @brief Utility methods for parsing HTTP requests and responses
* @headerfile qhttpparser.h QHttpEngine/QHttpParser
*
* This class provides a set of static methods for parsing HTTP request and
* response headers. Functionality is broken up into smaller methods in order
* to make the unit tests simpler.
*/
class QHTTPENGINE_EXPORT QHttpParser
class QHTTPENGINE_EXPORT Parser
{
public:
@@ -60,7 +54,12 @@ public:
* items. If maxSplit is equal to zero, there will be no limit on the
* number of splits performed.
*/
static void split(const QByteArray &data, const QByteArray &delim, int maxSplit, QList<QByteArray> &parts);
static void split(const QByteArray &data, const QByteArray &delim, int maxSplit, QByteArrayList &parts);
/**
* @brief Parse and remove the query string from a path
*/
static bool parsePath(const QByteArray &rawPath, QString &path, Socket::QueryStringMap &queryString);
/**
* @brief Parse a list of lines containing HTTP headers
@@ -68,7 +67,7 @@ public:
* Each line is expected to be in the format "name: value". Parsing is
* immediately aborted if an invalid line is encountered.
*/
static bool parseHeaderList(const QList<QByteArray> &lines, QHttpHeaderMap &headers);
static bool parseHeaderList(const QList<QByteArray> &lines, Socket::HeaderMap &headers);
/**
* @brief Parse HTTP headers
@@ -77,17 +76,19 @@ public:
* into a status line and HTTP headers. The parts list will contain the
* parts from the status line.
*/
static bool parseHeaders(const QByteArray &data, QList<QByteArray> &parts, QHttpHeaderMap &headers);
static bool parseHeaders(const QByteArray &data, QList<QByteArray> &parts, Socket::HeaderMap &headers);
/**
* @brief Parse HTTP request headers
*/
static bool parseRequestHeaders(const QByteArray &data, QByteArray &method, QByteArray &path, QHttpHeaderMap &headers);
static bool parseRequestHeaders(const QByteArray &data, Socket::Method &method, QByteArray &path, Socket::HeaderMap &headers);
/**
* @brief Parse HTTP response headers
*/
static bool parseResponseHeaders(const QByteArray &data, int &statusCode, QByteArray &statusReason, QHttpHeaderMap &headers);
static bool parseResponseHeaders(const QByteArray &data, int &statusCode, QByteArray &statusReason, Socket::HeaderMap &headers);
};
#endif // QHTTPENGINE_QHTTPPARSER_H
}
#endif // QHTTPENGINE_PARSER_H

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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 QHTTPENGINE_PROXYHANDLER_H
#define QHTTPENGINE_PROXYHANDLER_H
#include <QHostAddress>
#include <qhttpengine/handler.h>
#include "qhttpengine_export.h"
namespace QHttpEngine
{
class QHTTPENGINE_EXPORT ProxyHandlerPrivate;
/**
* @brief %Handler that routes HTTP requests to an upstream server
*/
class QHTTPENGINE_EXPORT ProxyHandler : public Handler
{
Q_OBJECT
public:
/**
* @brief Create a new proxy handler
*/
ProxyHandler(const QHostAddress &address, quint16 port, QObject *parent = 0);
protected:
/**
* @brief Reimplementation of [Handler::process()](QHttpEngine::Handler::process)
*/
virtual void process(Socket *socket, const QString &path);
private:
ProxyHandlerPrivate *const d;
};
}
#endif // QHTTPENGINE_PROXYHANDLER_H

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -23,16 +23,19 @@
#ifndef QHTTPENGINE_QIODEVICECOPIER_H
#define QHTTPENGINE_QIODEVICECOPIER_H
#include <QIODevice>
#include <QObject>
#include "qhttpengine.h"
#include "qhttpengine_export.h"
class QIODevice;
namespace QHttpEngine
{
class QHTTPENGINE_EXPORT QIODeviceCopierPrivate;
/**
* @brief Data copier for classes deriving from QIODevice
* @headerfile qiodevicecopier.h QHttpEngine/QIODeviceCopier
*
* QIODeviceCopier provides a set of methods for reading data from a QIODevice
* and writing it to another. The class operates asynchronously and therefore
@@ -43,7 +46,7 @@ class QHTTPENGINE_EXPORT QIODeviceCopierPrivate;
* QFile srcFile("src.txt");
* QFile destFile("dest.txt");
*
* QIODeviceCopier copier(&srcFile, &destFile);
* QHttpEngine::QIODeviceCopier copier(&srcFile, &destFile);
* copier.start()
* @endcode
*
@@ -76,6 +79,11 @@ public:
*/
void setBufferSize(qint64 size);
/**
* @brief Set range of data to copy, if src device is not sequential
*/
void setRange(qint64 from, qint64 to);
Q_SIGNALS:
/**
@@ -122,4 +130,6 @@ private:
friend class QIODeviceCopierPrivate;
};
}
#endif // QHTTPENGINE_QIODEVICECOPIER_H

View File

@@ -0,0 +1,196 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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 QHTTPENGINE_QOBJECTHANDLER_H
#define QHTTPENGINE_QOBJECTHANDLER_H
#include <qhttpengine/handler.h>
#include "qhttpengine_export.h"
namespace QHttpEngine
{
class Socket;
class QHTTPENGINE_EXPORT QObjectHandlerPrivate;
/**
* @brief %Handler for invoking slots
*
* This handler enables incoming requests to be processed by slots in a
* QObject-derived class or functor. Methods are registered by providing a
* name and slot to invoke. The slot must take a pointer to the
* [Socket](@ref QHttpEngine::Socket) for the request as an argument and
* must also close the socket when finished with it.
*
* To use this class, simply create an instance and call the appropriate
* registerMethod() overload. For example:
*
* @code
* class Object : public QObject
* {
* Q_OBJECT
* public slots:
* void something(QHttpEngine::Socket *socket);
* };
*
* QHttpEngine::QObjectHandler handler;
* Object object;
* // Old connection syntax
* handler.registerMethod("something", &object, SLOT(something(QHttpEngine::Socket*)));
* // New connection syntax
* handler.registerMethod("something", &object, &Object::something);
* @endcode
*
* It is also possible to use this class with a functor, eliminating the need
* to create a class and slot:
*
* @code
* QHttpEngine::QObjectHandler handler;
* handler.registerMethod("something", [](QHttpEngine::Socket *socket) {
* // do something
* socket->close();
* });
* @endcode
*/
class QHTTPENGINE_EXPORT QObjectHandler : public Handler
{
Q_OBJECT
public:
/**
* @brief Create a new QObject handler
*/
explicit QObjectHandler(QObject *parent = 0);
/**
* @brief Register a method
*
* This overload uses the traditional connection syntax with macros.
*
* The readAll parameter determines whether all data must be received by
* the socket before invoking the slot.
*/
void registerMethod(const QString &name, QObject *receiver, const char *method, bool readAll = true);
#ifdef DOXYGEN
/**
* @brief Register a method
*
* This overload uses the new connection syntax with member pointers.
*/
void registerMethod(const QString &name, QObject *receiver, PointerToMemberFunction method, bool readAll = true);
/**
* @brief Register a method
*
* This overload uses the new functor syntax (without context).
*/
void registerMethod(const QString &name, Functor functor, bool readAll = true);
/**
* @brief Register a method
*
* This overload uses the new functor syntax (with context).
*/
void registerMethod(const QString &name, QObject *receiver, Functor functor, bool readAll = true);
#else
template <typename Func1>
inline void registerMethod(const QString &name,
typename QtPrivate::FunctionPointer<Func1>::Object *receiver,
Func1 slot,
bool readAll = true) {
typedef QtPrivate::FunctionPointer<Func1> SlotType;
// Ensure the slot doesn't have too many arguments
Q_STATIC_ASSERT_X(int(SlotType::ArgumentCount) == 1,
"The slot must have exactly one argument.");
// Ensure the argument is of the correct type
Q_STATIC_ASSERT_X((QtPrivate::AreArgumentsCompatible<Socket*, typename QtPrivate::List_Select<typename SlotType::Arguments, 0>::Value>::value),
"The slot parameters do not match");
// Invoke the implementation
registerMethodImpl(name, receiver, new QtPrivate::QSlotObject<Func1, typename SlotType::Arguments, void>(slot), readAll);
}
template <typename Func1>
inline typename QtPrivate::QEnableIf<!QtPrivate::AreArgumentsCompatible<Func1, QObject*>::value, void>::Type
registerMethod(const QString &name, Func1 slot, bool readAll = true) {
registerMethod(name, Q_NULLPTR, slot, readAll);
}
template <typename Func1>
inline typename QtPrivate::QEnableIf<!QtPrivate::FunctionPointer<Func1>::IsPointerToMemberFunction &&
#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
!std::is_same<const char*, Func1>::value,
#else
!QtPrivate::is_same<const char*, Func1>::value,
#endif
void>::Type
registerMethod(const QString &name, QObject *context, Func1 slot, bool readAll = true) {
// There is an easier way to do this but then the header wouldn't
// compile on non-C++11 compilers
return registerMethod_functor(name, context, slot, &Func1::operator(), readAll);
}
#endif
protected:
/**
* @brief Reimplementation of [Handler::process()](QHttpEngine::Handler::process)
*/
virtual void process(Socket *socket, const QString &path);
private:
template <typename Func1, typename Func1Operator>
inline void registerMethod_functor(const QString &name, QObject *context, Func1 slot, Func1Operator, bool readAll) {
typedef QtPrivate::FunctionPointer<Func1Operator> SlotType;
// Ensure the slot doesn't have too many arguments
Q_STATIC_ASSERT_X(int(SlotType::ArgumentCount) == 1,
"The slot must have exactly one argument.");
// Ensure the argument is of the correct type
Q_STATIC_ASSERT_X((QtPrivate::AreArgumentsCompatible<Socket*, typename QtPrivate::List_Select<typename SlotType::Arguments, 0>::Value>::value),
"The slot parameters do not match");
registerMethodImpl(name, context,
new QtPrivate::QFunctorSlotObject<Func1, 1, typename SlotType::Arguments, void>(slot),
readAll);
}
void registerMethodImpl(const QString &name, QObject *receiver, QtPrivate::QSlotObjectBase *slotObj, bool readAll);
QObjectHandlerPrivate *const d;
friend class QObjectHandlerPrivate;
};
}
#endif // QHTTPENGINE_QOBJECTHANDLER_H

View File

@@ -0,0 +1,200 @@
/*
* Copyright (c) 2017 Aleksei Ermakov
*
* 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 QHTTPENGINE_RANGE_H
#define QHTTPENGINE_RANGE_H
#include <QString>
#include "qhttpengine_export.h"
namespace QHttpEngine
{
class QHTTPENGINE_EXPORT RangePrivate;
/**
* @brief HTTP range representation
*
* This class provides a representation of HTTP range, described in RFC 7233
* and used when partial content is requested by the client. When an object is
* created, optional dataSize can be specified, so that relative ranges can
* be represented as absolute.
*
* @code
* QHttpEngine::Range range(10, -1, 90);
* range.from(); // 10
* range.to(); // 89
* range.length(); // 80
*
* range = QHttpEngine::Range("-500", 1000);
* range.from(); // 500
* range.to(); // 999
* range.length(); // 500
*
* range = QHttpEngine::Range(0, -1);
* range.from(); // 0
* range.to(); // -1
* range.length(); // -1
*
* range = QHttpEngine::Range(range, 100);
* range.from(); // 0
* range.to(); // 99
* range.length(); // 100
* @endcode
*
*/
class QHTTPENGINE_EXPORT Range
{
public:
/**
* @brief Create a new range
*
* An empty Range is considered invalid.
*/
Range();
/**
* @brief Construct a range from the provided string
*
* Parses string representation range and constructs new Range. For raw
* header "Range: bytes=0-100" only "0-100" should be passed to
* constructor. dataSize may be supplied so that relative ranges could be
* represented as absolute values.
*/
Range(const QString &range, qint64 dataSize = -1);
/**
* @brief Construct a range from the provided offsets
*
* Initialises a new Range with from and to values. dataSize may be
* supplied so that relative ranges could be represented as absolute
* values.
*/
Range(qint64 from, qint64 to, qint64 dataSize = -1);
/**
* @brief Construct a range from the another range's offsets
*
* Initialises a new Range with from and to values of other Range.
* Supplied dataSize is used instead of other dataSize.
*/
Range(const Range &other, qint64 dataSize);
/**
* @brief Destroy the range
*/
~Range();
/**
* @brief Assignment operator
*/
Range& operator=(const Range &other);
/**
* @brief Retrieve starting position of range
*
* If range is set as 'last N bytes' and dataSize is not set, returns -N.
*/
qint64 from() const;
/**
* @brief Retrieve ending position of range
*
* If range is set as 'last N bytes' and dataSize is not set, returns -1.
* If ending position is not set, and dataSize is not set, returns -1.
*/
qint64 to() const;
/**
* @brief Retrieve length of range
*
* If ending position is not set, and dataSize is not set, and range is
* not set as 'last N bytes', returns -1. If range is invalid, returns -1.
*/
qint64 length() const;
/**
* @brief Retrieve dataSize of range
*
* If dataSize is not set, this method returns -1.
*/
qint64 dataSize() const;
/**
* @brief Checks if range is valid
*
* Range is considered invalid if it is out of bounds, that is when this
* inequality is false - (from <= to < dataSize).
*
* When QHttpRange(const QString&) fails to parse range string, resulting
* range is also considered invalid.
*
* @code
* QHttpEngine::Range range(1, 0, -1);
* range.isValid(); // false
*
* range = QHttpEngine::Range(512, 1024);
* range.isValid(); // true
*
* range = QHttpEngine::Range("-");
* range.isValid(); // false
*
* range = QHttpEngine::Range("abccbf");
* range.isValid(); // false
*
* range = QHttpEngine::Range(0, 512, 128);
* range.isValid(); // false
*
* range = QHttpEngine::Range(128, 64, 512);
* range.isValid(); // false
* @endcode
*/
bool isValid() const;
/**
* @brief Retrieve representation suitable for Content-Range header
*
* @code
* QHttpEngine::Range range(0, 100, 1000);
* range.contentRange(); // "0-100/1000"
*
* // When resource size is unknown
* range = QHttpEngine::Range(512, 1024);
* range.contentRange(); // "512-1024/*"
*
* // if range request was bad, return resource size
* range = QHttpEngine::Range(1, 0, 1200);
* range.contentRange(); // "*\/1200"
* @endcode
*/
QString contentRange() const;
private:
RangePrivate *const d;
};
}
#endif // QHTTPENGINE_RANGE_H

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -20,44 +20,53 @@
* IN THE SOFTWARE.
*/
#ifndef QHTTPENGINE_QHTTPSERVER_H
#define QHTTPENGINE_QHTTPSERVER_H
#ifndef QHTTPENGINE_SERVER_H
#define QHTTPENGINE_SERVER_H
#include <QHostAddress>
#include <QObject>
#include <QTcpServer>
#include "qhttpengine.h"
#include "qhttphandler.h"
#include "qhttpengine_export.h"
class QHTTPENGINE_EXPORT QHttpServerPrivate;
#if !defined(QT_NO_SSL)
class QSslConfiguration;
#endif
namespace QHttpEngine
{
class Handler;
class QHTTPENGINE_EXPORT ServerPrivate;
/**
* @brief TCP server for HTTP requests
* @headerfile qhttpserver.h QHttpEngine/QHttpServer
*
* This class provides a TCP server that listens for HTTP requests on the
* specified address and port. When a new request is received, a QHttpSocket
* is created for the QTcpSocket which abstracts a TCP server socket. Once the
* request headers are received, the root handler is invoked and the request
* processed. The QHttpSocket assumes ownership of the QTcpSocket.
* specified address and port. When a new request is received, a
* [Socket](@ref QHttpEngine::Socket) is created for the QTcpSocket which
* abstracts a TCP server socket. Once the request headers are received, the
* root handler is invoked and the request processed. The server assumes
* ownership of the QTcpSocket.
*
* Because QHttpServer derives from QTcpServer, instructing the server to
* listen on an available port is as simple as invoking listen() with no
* parameters:
* Because [Server](@ref QHttpEngine::Server) derives from QTcpServer,
* instructing the server to listen on an available port is as simple as
* invoking listen() with no parameters:
*
* @code
* QHttpServer server;
* if(!server.listen()) {
* QHttpEngine::Server server;
* if (!server.listen()) {
* // error handling
* }
* @endcode
*
* Before passing the socket to the handler, the QTcpSocket's disconnected()
* signal is connected to the QHttpSocket's deleteLater() slot to ensure that
* the socket is deleted when the client disconnects.
* signal is connected to the [Socket](@ref QHttpEngine::Socket)'s
* deleteLater() slot to ensure that the socket is deleted when the client
* disconnects.
*/
class QHTTPENGINE_EXPORT QHttpServer : public QTcpServer
class QHTTPENGINE_EXPORT Server : public QTcpServer
{
Q_OBJECT
@@ -66,22 +75,41 @@ public:
/**
* @brief Create an HTTP server
*/
explicit QHttpServer(QObject *parent = 0);
explicit Server(QObject *parent = 0);
/**
* @brief Create an HTTP server with the specified handler
*/
QHttpServer(QHttpHandler *handler, QObject *parent = 0);
Server(Handler *handler, QObject *parent = 0);
/**
* @brief Set the root handler for all new requests
*/
void setHandler(QHttpHandler *handler);
void setHandler(Handler *handler);
#if !defined(QT_NO_SSL)
/**
* @brief Set the SSL configuration for the server
*
* If the configuration is not NULL, the server will begin negotiating
* connections using SSL/TLS.
*/
void setSslConfiguration(const QSslConfiguration &configuration);
#endif
protected:
/**
* @brief Implementation of QTcpServer::incomingConnection()
*/
void incomingConnection(qintptr socketDescriptor);
private:
QHttpServerPrivate *const d;
friend class QHttpServerPrivate;
ServerPrivate *const d;
friend class ServerPrivate;
};
#endif // QHTTPENGINE_QHTTPSERVER_H
}
#endif // QHTTPENGINE_SERVER_H

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -20,32 +20,40 @@
* IN THE SOFTWARE.
*/
#ifndef QHTTPENGINE_QHTTPSOCKET_H
#define QHTTPENGINE_QHTTPSOCKET_H
#ifndef QHTTPENGINE_SOCKET_H
#define QHTTPENGINE_SOCKET_H
#include <QTcpSocket>
#include <QHostAddress>
#include <QIODevice>
#include <QMultiMap>
#include "qhttpengine.h"
#include "qhttpparser.h"
#include <qhttpengine/ibytearray.h>
class QHTTPENGINE_EXPORT QHttpSocketPrivate;
#include "qhttpengine_export.h"
class QJsonDocument;
class QTcpSocket;
namespace QHttpEngine
{
class QHTTPENGINE_EXPORT SocketPrivate;
/**
* @brief Implementation of the HTTP protocol
* @headerfile qhttpsocket.h QHttpEngine/QHttpSocket
*
* QHttpSocket provides a class derived from QIODevice that can be used to
* read data from and write data to an HTTP client through a QTcpSocket
* provided in the constructor. The QHttpSocket will assume ownership of the
* socket and ensure it is properly deleted. Consequently, the QTcpSocket must
* have been allocated on the heap:
* This class provides a class derived from QIODevice that can be used to read
* data from and write data to an HTTP client through a QTcpSocket provided in
* the constructor. The socket will assume ownership of the QTcpSocket and
* ensure it is properly deleted. Consequently, the QTcpSocket must have been
* allocated on the heap:
*
* @code
* QTcpSocket *tcpSock = new QTcpSocket;
* tcpSock->connectToHost(...);
* tcpSock->waitForConnected();
*
* QHttpSocket *httpSock = new QHttpSocket(tcpSock);
* QHttpEngine::Socket *httpSock = new QHttpEngine::Socket(tcpSock);
* @endcode
*
* Once the headersParsed() signal is emitted, information about the request
@@ -85,41 +93,96 @@ class QHTTPENGINE_EXPORT QHttpSocketPrivate;
* status code to the writeError() method. Both methods will close the socket
* once the response is written.
*/
class QHTTPENGINE_EXPORT QHttpSocket : public QIODevice
class QHTTPENGINE_EXPORT Socket : public QIODevice
{
Q_OBJECT
public:
/**
* @brief Map consisting of query string values
*/
typedef QMultiMap<QString, QString> QueryStringMap;
/**
* @brief Map consisting of HTTP headers
*
* The key used for the map is the
* [IByteArray](@ref QHttpEngine::IByteArray) class, which allows for
* case-insensitive comparison.
*/
typedef QMultiMap<IByteArray, QByteArray> HeaderMap;
/**
* HTTP methods
*
* An integer constant is provided for each of the methods described in
* RFC 2616 (HTTP/1.1).
*/
enum Method {
/// Request for communications options
OPTIONS = 1,
/// Request resource
GET = 1 << 1,
/// Request resource without body
HEAD = 1 << 2,
/// Store subordinate resource
POST = 1 << 3,
/// Store resource
PUT = 1 << 4,
/// Delete resource
DELETE = 1 << 5,
/// Diagnostic trace
TRACE = 1 << 6,
/// Proxy connection
CONNECT = 1 << 7
};
/**
* Predefined constants for HTTP status codes
*/
enum {
/// Request was successful
OK = 200,
/// Request was successful and a resource was created
Created = 201,
/// Request was accepted for processing, not completed yet.
Accepted = 202,
/// %Range request was successful
PartialContent = 206,
/// Resource has moved permanently
MovedPermanently = 301,
/// Resource is available at an alternate URI
Found = 302,
/// Bad client request
BadRequest = 400,
/// Client is unauthorized to access the resource
Unauthorized = 401,
/// Access to the resource is forbidden
Forbidden = 403,
/// Resource was not found
NotFound = 404,
/// Method is not valid for the resource
MethodNotAllowed = 405,
/// The request could not be completed due to a conflict with the current state of the resource
Conflict = 409,
/// An internal server error occurred
InternalServerError = 500
InternalServerError = 500,
/// Invalid response from server while acting as a gateway
BadGateway = 502,
/// %Server unable to handle request due to overload
ServiceUnavailable = 503,
/// %Server does not supports the HTTP version in the request
HttpVersionNotSupported = 505
};
/**
* @brief Create a new QHttpSocket from a QTcpSocket
* @brief Create a new socket from a QTcpSocket
*
* This instance will assume ownership of the socket. That is, it will
* This instance will assume ownership of the QTcpSocket. That is, it will
* make itself the parent of the socket.
*/
QHttpSocket(QTcpSocket *socket, QObject *parent = 0);
Socket(QTcpSocket *socket, QObject *parent = 0);
/**
* @brief Retrieve the number of bytes available for reading
@@ -140,30 +203,52 @@ public:
* @brief Close the device and underlying socket
*
* Invoking this method signifies that no more data will be written to the
* device. It will also close the underlying QTcpSocket.
* device. It will also close the underlying QTcpSocket and destroy this
* object.
*/
virtual void close();
/**
* @brief Retrive the address of the remote peer
*/
QHostAddress peerAddress() const;
/**
* @brief Determine if the request headers have been parsed yet
*/
bool isHeadersParsed() const;
/**
* @brief Retrieve the request method
*
* This method may only be called after the request headers have been
* parsed.
*/
QByteArray method() const;
Method method() const;
/**
* @brief Retrieve the request path
* @brief Retrieve the raw request path
*
* This method may only be called after the request headers have been
* parsed.
*/
QByteArray path() const;
QByteArray rawPath() const;
/**
* @brief Determine if the request headers have been parsed yet
* @brief Retrieve the decoded path with the query string removed
*
* This method may only be called after the request headers have been
* parsed.
*/
bool isHeadersParsed() const;
QString path() const;
/**
* @brief Retrieve the query string
*
* This method may only be called after the request headers have been
* parsed.
*/
QueryStringMap queryString() const;
/**
* @brief Retrieve a map of request headers
@@ -172,7 +257,7 @@ public:
* parsed. The original case of the headers is preserved but comparisons
* are performed in a case-insensitive manner.
*/
QHttpHeaderMap headers() const;
HeaderMap headers() const;
/**
* @brief Retrieve the length of the content
@@ -182,6 +267,22 @@ public:
*/
qint64 contentLength() const;
/**
* @brief Parse the request body as a JSON document
*
* This method may only be called after the request headers **and** the
* request body have been received. The most effective way to confirm that
* this is the case is by using:
*
* @code
* socket->bytesAvailable() >= socket->contentLength()
* @endcode
*
* If the JSON received is invalid, an error will be immediately written
* to the socket. The return value indicates whether the JSON was valid.
*/
bool readJson(QJsonDocument &document);
/**
* @brief Set the response code
*
@@ -197,10 +298,10 @@ public:
* @brief Set a response header to a specific value
*
* This method may only be called before the response headers are written.
* If the specified header already has a value set, it will be
* overwritten.
* Duplicate values will be either appended to the header or used to
* replace the original value, depending on the third parameter.
*/
void setHeader(const QByteArray &name, const QByteArray &value);
void setHeader(const QByteArray &name, const QByteArray &value, bool replace = true);
/**
* @brief Set the response headers
@@ -208,7 +309,7 @@ public:
* This method may only be called before the response headers are written.
* All existing headers will be overwritten.
*/
void setHeaders(const QHttpHeaderMap &headers);
void setHeaders(const HeaderMap &headers);
/**
* @brief Write response headers to the socket
@@ -219,15 +320,20 @@ public:
void writeHeaders();
/**
* @brief Write an HTTP 3xx redirect to the socket
* @brief Write an HTTP 3xx redirect to the socket and close it
*/
void writeRedirect(const QByteArray &path, bool permanent = false);
/**
* @brief Write an HTTP error to the socket
* @brief Write an HTTP error to the socket and close it
*/
void writeError(int statusCode, const QByteArray &statusReason = QByteArray());
/**
* @brief Write the specified JSON document to the socket and close it
*/
void writeJson(const QJsonDocument &document, int statusCode = OK);
Q_SIGNALS:
/**
@@ -240,6 +346,11 @@ Q_SIGNALS:
*/
void headersParsed();
/**
* @brief Indicate that the client has disconnected
*/
void disconnected();
protected:
/**
@@ -254,8 +365,10 @@ protected:
private:
QHttpSocketPrivate *const d;
friend class QHttpSocketPrivate;
SocketPrivate *const d;
friend class SocketPrivate;
};
#endif // QHTTPENGINE_QHTTPSOCKET_H
}
#endif // QHTTPENGINE_SOCKET_H

View File

@@ -6,5 +6,6 @@ Name: @PROJECT_NAME@
Description: @PROJECT_DESCRIPTION@
URL: @PROJECT_URL@
Version: @PROJECT_VERSION@
Requires: Qt5Network
Cflags: -I${includedir}
Libs: -L${libdir} -l@PROJECT_NAME@
Libs: -L${libdir} -lqhttpengine

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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 QHTTPENGINE_QHTTPENGINE_H
#define QHTTPENGINE_QHTTPENGINE_H
#include <QtCore/qglobal.h>
#define QHTTPENGINE_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define QHTTPENGINE_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define QHTTPENGINE_VERSION_PATCH @PROJECT_VERSION_PATCH@
#define QHTTPENGINE_VERSION "@PROJECT_VERSION@"
#cmakedefine BUILD_SHARED_LIBS
#if defined(BUILD_SHARED_LIBS)
# if defined(QHTTPENGINE_LIBRARY)
# define QHTTPENGINE_EXPORT Q_DECL_EXPORT
# else
# define QHTTPENGINE_EXPORT Q_DECL_IMPORT
# endif
#else
# define QHTTPENGINE_EXPORT
#endif
#endif // QHTTPENGINE_QHTTPENGINE_H

View File

@@ -1,84 +0,0 @@
/*
* Copyright (c) 2015 Nathan Osman
*
* 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.
*/
#include <QTcpSocket>
#include "QHttpEngine/qhttpserver.h"
#include "QHttpEngine/qhttpsocket.h"
#include "qhttpserver_p.h"
QHttpServerPrivate::QHttpServerPrivate(QHttpServer *httpServer)
: QObject(httpServer),
q(httpServer),
handler(0)
{
connect(q, SIGNAL(newConnection()), this, SLOT(onIncomingConnection()));
}
void QHttpServerPrivate::onIncomingConnection()
{
// Obtain the next pending connection and create a QHttpSocket from it
QTcpSocket *tcpSocket = q->nextPendingConnection();
QHttpSocket *httpSocket = new QHttpSocket(tcpSocket, this);
// Wait until the socket finishes reading the HTTP headers before routing
connect(httpSocket, SIGNAL(headersParsed()), this, SLOT(onHeadersParsed()));
// Destroy the socket once the client is disconnected
connect(tcpSocket, SIGNAL(disconnected()), httpSocket, SLOT(deleteLater()));
}
void QHttpServerPrivate::onHeadersParsed()
{
// Obtain the socket that corresponds with the sender of the signal
QHttpSocket *socket = qobject_cast<QHttpSocket*>(sender());
// Ensure that a handler has been set
if(handler) {
// Obtain the path, strip the initial "/", and pass it along to the handler
handler->route(socket, QString(socket->path().mid(1)));
} else {
// Return an HTTP 500 error to the client
socket->writeError(QHttpSocket::InternalServerError);
}
}
QHttpServer::QHttpServer(QObject *parent)
: QTcpServer(parent),
d(new QHttpServerPrivate(this))
{
}
QHttpServer::QHttpServer(QHttpHandler *handler, QObject *parent)
: QTcpServer(parent),
d(new QHttpServerPrivate(this))
{
setHandler(handler);
}
void QHttpServer::setHandler(QHttpHandler *handler)
{
d->handler = handler;
}

View File

@@ -1,323 +0,0 @@
/*
* Copyright (c) 2015 Nathan Osman
*
* 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.
*/
#include <cstring>
#include "QHttpEngine/qhttpsocket.h"
#include "qhttpsocket_p.h"
// Predefined error response requires a simple HTML template to be returned to
// the client describing the error condition
const QString ErrorTemplate =
"<!DOCTYPE html><html><head><meta charset=\"utf-8\">"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<title>%1 %2</title></head><body><h1>%1 %2</h1><p>"
"An error has occurred while trying to display the requested resource. "
"Please contact the website owner if this error persists."
"</p><hr><p><em>QHttpEngine %3</em></p></body></html>";
QHttpSocketPrivate::QHttpSocketPrivate(QHttpSocket *httpSocket, QTcpSocket *tcpSocket)
: QObject(httpSocket),
q(httpSocket),
socket(tcpSocket),
readState(ReadHeaders),
requestDataRead(0),
requestDataTotal(-1),
writeState(WriteNone),
responseStatusCode(200),
responseStatusReason(statusReason(200))
{
socket->setParent(this);
connect(socket, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
connect(socket, SIGNAL(bytesWritten(qint64)), this, SLOT(onBytesWritten(qint64)));
// Process anything already received by the socket
onReadyRead();
}
QByteArray QHttpSocketPrivate::statusReason(int statusCode) const
{
switch(statusCode) {
case QHttpSocket::OK: return "OK";
case QHttpSocket::MovedPermanently: return "MOVED PERMANENTLY";
case QHttpSocket::Found: return "FOUND";
case QHttpSocket::BadRequest: return "BAD REQUEST";
case QHttpSocket::Forbidden: return "FORBIDDEN";
case QHttpSocket::NotFound: return "NOT FOUND";
case QHttpSocket::MethodNotAllowed: return "METHOD NOT ALLOWED";
case QHttpSocket::InternalServerError: return "INTERNAL SERVER ERROR";
default: return "UNKNOWN";
}
}
void QHttpSocketPrivate::onReadyRead()
{
// Append all of the new data to the read buffer
readBuffer.append(socket->readAll());
// If reading headers, return if they could not be read (yet)
if(readState == ReadHeaders) {
if(!readHeaders()) {
return;
}
}
if(readState == ReadData) {
readData();
} else if(readState == ReadFinished) {
// Any data received here is unexpected and should be ignored
readBuffer.clear();
}
}
void QHttpSocketPrivate::onBytesWritten(qint64 bytes)
{
// Check to see if all of the response header was written
if(writeState == WriteHeaders) {
if(responseHeaderRemaining - bytes > 0) {
responseHeaderRemaining -= bytes;
} else {
writeState = WriteData;
bytes -= responseHeaderRemaining;
}
}
if(writeState == WriteData) {
Q_EMIT q->bytesWritten(bytes);
}
}
bool QHttpSocketPrivate::readHeaders()
{
// Check for the double CRLF that signals the end of the headers and
// if it is not found, wait until the next time readyRead is emitted
int index = readBuffer.indexOf("\r\n\r\n");
if(index == -1) {
return false;
}
// Attempt to parse the headers and if a problem is encountered, abort
// the connection (so that no more data is read or written) and return
if(!QHttpParser::parseRequestHeaders(readBuffer.left(index), requestMethod, requestPath, requestHeaders)) {
q->writeError(QHttpSocket::BadRequest);
return false;
}
// Remove the headers from the buffer
readBuffer.remove(0, index + 4);
// Check for the content-length header - if it is present, then
// prepare to read the specified amount of data, otherwise, no data
// should be read from the socket and the read channel is finished
if(requestHeaders.contains("Content-Length")) {
readState = ReadData;
requestDataTotal = requestHeaders.value("Content-Length").toLongLong();
} else {
readState = ReadFinished;
}
// Indicate that the headers have been parsed
Q_EMIT q->headersParsed();
// If the new readState is ReadFinished, then indicate so
if(readState == ReadFinished) {
Q_EMIT q->readChannelFinished();
}
return true;
}
void QHttpSocketPrivate::readData()
{
// Emit the readyRead() signal if any data is available in the buffer
if(readBuffer.size()) {
Q_EMIT q->readyRead();
}
// Check to see if the specified amount of data has been read from the
// socket, if so, emit the readChannelFinished() signal
if(requestDataRead + readBuffer.size() >= requestDataTotal) {
readState = ReadFinished;
Q_EMIT q->readChannelFinished();
}
}
QHttpSocket::QHttpSocket(QTcpSocket *socket, QObject *parent)
: QIODevice(parent),
d(new QHttpSocketPrivate(this, socket))
{
// The device is initially open for both reading and writing
setOpenMode(QIODevice::ReadWrite);
}
qint64 QHttpSocket::bytesAvailable() const
{
if(d->readState > QHttpSocketPrivate::ReadHeaders) {
return d->readBuffer.size() + QIODevice::bytesAvailable();
} else {
return 0;
}
}
bool QHttpSocket::isSequential() const
{
return true;
}
void QHttpSocket::close()
{
// Invoke the parent method
QIODevice::close();
d->readState = QHttpSocketPrivate::ReadFinished;
d->writeState = QHttpSocketPrivate::WriteFinished;
d->socket->close();
}
QByteArray QHttpSocket::method() const
{
return d->requestMethod;
}
QByteArray QHttpSocket::path() const
{
return d->requestPath;
}
bool QHttpSocket::isHeadersParsed() const
{
return d->readState > QHttpSocketPrivate::ReadHeaders;
}
QHttpHeaderMap QHttpSocket::headers() const
{
return d->requestHeaders;
}
qint64 QHttpSocket::contentLength() const
{
return d->requestDataTotal;
}
void QHttpSocket::setStatusCode(int statusCode, const QByteArray &statusReason)
{
d->responseStatusCode = statusCode;
d->responseStatusReason = statusReason.isNull() ? d->statusReason(statusCode) : statusReason;
}
void QHttpSocket::setHeader(const QByteArray &name, const QByteArray &value)
{
d->responseHeaders.insert(name, value);
}
void QHttpSocket::setHeaders(const QHttpHeaderMap &headers)
{
d->responseHeaders = headers;
}
void QHttpSocket::writeHeaders()
{
// Use a QByteArray for building the header so that we can later determine
// exactly how many bytes were written
QByteArray header;
// Append the status line
header.append("HTTP/1.0 ");
header.append(QByteArray::number(d->responseStatusCode) + " " + d->responseStatusReason);
header.append("\r\n");
// Append each of the headers followed by a CRLF
for(QHttpHeaderMap::const_iterator i = d->responseHeaders.constBegin(); i != d->responseHeaders.constEnd(); ++i) {
header.append(i.key());
header.append(": ");
header.append(i.value());
header.append("\r\n");
}
// Append an extra CRLF
header.append("\r\n");
d->writeState = QHttpSocketPrivate::WriteHeaders;
d->responseHeaderRemaining = header.length();
// Write the header
d->socket->write(header);
}
void QHttpSocket::writeRedirect(const QByteArray &path, bool permanent)
{
setStatusCode(permanent ? MovedPermanently : Found);
setHeader("Location", path);
writeHeaders();
close();
}
void QHttpSocket::writeError(int statusCode, const QByteArray &statusReason)
{
setStatusCode(statusCode, statusReason);
// Build the template that will be sent to the client
QByteArray data = ErrorTemplate
.arg(d->responseStatusCode)
.arg(d->responseStatusReason.constData())
.arg(QHTTPENGINE_VERSION)
.toUtf8();
setHeader("Content-Length", QByteArray::number(data.length()));
setHeader("Content-Type", "text/html");
writeHeaders();
write(data);
close();
}
qint64 QHttpSocket::readData(char *data, qint64 maxlen)
{
// Ensure the connection is in the correct state for reading data
if(d->readState == QHttpSocketPrivate::ReadHeaders) {
return 0;
}
// Ensure that no more than the requested amount or the size of the buffer is read
qint64 size = qMin(static_cast<qint64>(d->readBuffer.size()), maxlen);
memcpy(data, d->readBuffer.constData(), size);
// Remove the amount that was read from the buffer
d->readBuffer.remove(0, size);
d->requestDataRead += size;
return size;
}
qint64 QHttpSocket::writeData(const char *data, qint64 len)
{
// If the response headers have not yet been written, they must
// immediately be written before the data can be
if(d->writeState == QHttpSocketPrivate::WriteNone) {
writeHeaders();
}
return d->socket->write(data, len);
}

View File

@@ -1,71 +0,0 @@
/*
* Copyright (c) 2015 Nathan Osman
*
* 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.
*/
#include <QString>
#include "QHttpEngine/qibytearray.h"
QIByteArray::QIByteArray()
{}
QIByteArray::QIByteArray(const QByteArray &other)
: QByteArray(other)
{}
QIByteArray::QIByteArray(const char *data, int size)
: QByteArray(data, size)
{}
bool operator==(const QIByteArray &a1, const QIByteArray &a2)
{
return a1.toLower() == a2.toLower();
}
bool operator==(const QIByteArray &a1, const QString &a2)
{
return a1.toLower() == a2.toLower();
}
bool operator==(const QString &a1, const QIByteArray &a2)
{
return a2 == a1;
}
bool operator==(const QIByteArray &a1, const QByteArray &a2)
{
return a1.toLower() == a2.toLower();
}
bool operator==(const QByteArray &a1, const QIByteArray &a2)
{
return a2 == a1;
}
bool operator==(const QIByteArray &a1, const char *a2)
{
return a1.toLower() == QByteArray(a2).toLower();
}
bool operator==(const char *a1, const QIByteArray &a2)
{
return a2 == a1;
}

View File

@@ -1,127 +0,0 @@
/*
* Copyright (c) 2015 Nathan Osman
*
* 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.
*/
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QMetaMethod>
#include <QMetaObject>
#include <QMetaType>
#include <QVariantMap>
#include "QHttpEngine/qobjecthandler.h"
#include "qobjecthandler_p.h"
QObjectHandlerPrivate::QObjectHandlerPrivate(QObjectHandler *handler)
: QObject(handler),
q(handler)
{
}
void QObjectHandlerPrivate::invokeSlot(QHttpSocket *socket, int index)
{
// Attempt to decode the JSON from the socket
QJsonParseError error;
QJsonDocument document = QJsonDocument::fromJson(socket->readAll(), &error);
// Ensure that the document is valid
if(error.error != QJsonParseError::NoError) {
socket->writeError(QHttpSocket::BadRequest);
return;
}
// Attempt to invoke the slot
QVariantMap retVal;
if(!q->metaObject()->method(index).invoke(q,
Q_RETURN_ARG(QVariantMap, retVal),
Q_ARG(QVariantMap, document.object().toVariantMap()))) {
socket->writeError(QHttpSocket::InternalServerError);
return;
}
// Convert the return value to JSON and write it to the socket
QByteArray data = QJsonDocument(QJsonObject::fromVariantMap(retVal)).toJson();
socket->setHeader("Content-Length", QByteArray::number(data.length()));
socket->setHeader("Content-Type", "application/json");
socket->write(data);
socket->close();
}
void QObjectHandlerPrivate::onReadChannelFinished()
{
// Obtain the pointer to the socket emitting the signal
QHttpSocket *socket = qobject_cast<QHttpSocket*>(sender());
// Obtain the index and remove it from the map
int index = map.take(socket);
// Actually invoke the slot
invokeSlot(socket, index);
}
QObjectHandler::QObjectHandler(QObject *parent)
: QHttpHandler(parent),
d(new QObjectHandlerPrivate(this))
{
}
void QObjectHandler::process(QHttpSocket *socket, const QString &path)
{
// Only POST requests are accepted - reject any other methods but ensure
// that the Allow header is set in order to comply with RFC 2616
if(socket->method() != "POST") {
socket->setHeader("Allow", "POST");
socket->writeError(QHttpSocket::MethodNotAllowed);
return;
}
// Determine the index of the slot with the specified name - note that we
// don't need to worry about retrieving the index for deleteLater() since
// we specify the "QVariantMap" parameter type, which no parent slots use
int index = metaObject()->indexOfSlot(QString("%1(QVariantMap)").arg(path).toUtf8().data());
// If the index is invalid, the "resource" was not found
if(index == -1) {
socket->writeError(QHttpSocket::NotFound);
return;
}
// Ensure that the return type of the slot is QVariantMap
QMetaMethod method = metaObject()->method(index);
if(method.returnType() != QMetaType::QVariantMap) {
socket->writeError(QHttpSocket::InternalServerError);
return;
}
// Check to see if the socket has finished receiving all of the data yet
// or not - if so, jump to invokeSlot(), otherwise wait for the
// readChannelFinished() signal
if(socket->bytesAvailable() >= socket->contentLength()) {
d->invokeSlot(socket, index);
} else {
// Add the socket and index to the map so that the latter can be
// retrieved when the readChannelFinished() signal is emitted
d->map.insert(socket, index);
connect(socket, SIGNAL(readChannelFinished()), d, SLOT(onReadChannelFinished()));
}
}

View File

@@ -12,7 +12,7 @@ VS_VERSION_INFO VERSIONINFO
VALUE "FileDescription", "@PROJECT_DESCRIPTION@\0"
VALUE "FileVersion", "@PROJECT_VERSION@\0"
VALUE "InternalName", "@PROJECT_NAME@\0"
VALUE "LegalCopyright", "Copyright (c) 2015 @PROJECT_AUTHOR@\0"
VALUE "LegalCopyright", "Copyright (c) 2017 @PROJECT_AUTHOR@\0"
VALUE "OriginalFilename", "@OUTPUT_NAME@.dll\0"
VALUE "ProductName", "@PROJECT_NAME@\0"
VALUE "ProductVersion", "@PROJECT_VERSION@\0"

View File

@@ -0,0 +1,77 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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.
*/
#include <qhttpengine/basicauthmiddleware.h>
#include <qhttpengine/ibytearray.h>
#include <qhttpengine/parser.h>
#include <qhttpengine/socket.h>
#include "basicauthmiddleware_p.h"
using namespace QHttpEngine;
BasicAuthMiddlewarePrivate::BasicAuthMiddlewarePrivate(QObject *parent, const QString &realm)
: QObject(parent),
realm(realm)
{
}
BasicAuthMiddleware::BasicAuthMiddleware(const QString &realm, QObject *parent)
: Middleware(parent),
d(new BasicAuthMiddlewarePrivate(this, realm))
{
}
void BasicAuthMiddleware::add(const QString &username, const QString &password)
{
d->map.insert(username, password);
}
bool BasicAuthMiddleware::verify(const QString &username, const QString &password)
{
return d->map.contains(username) && d->map.value(username) == password;
}
bool BasicAuthMiddleware::process(Socket *socket)
{
// Attempt to extract credentials from the header
QByteArrayList headerParts = socket->headers().value("Authorization").split(' ');
if (headerParts.count() == 2 && headerParts.at(0) == IByteArray("Basic")) {
// Decode the credentials and split into username/password
QByteArrayList parts;
Parser::split(
QByteArray::fromBase64(headerParts.at(1)),
":", 1, parts
);
// Verify credentials
if (parts.count() == 2 && verify(parts.at(0), parts.at(1))) {
return true;
}
}
// Otherwise, inform the client that valid credentials are required
socket->setHeader("WWW-Authenticate", QString("Basic realm=\"%1\"").arg(d->realm).toUtf8());
socket->writeError(Socket::Unauthorized);
return false;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -20,34 +20,27 @@
* IN THE SOFTWARE.
*/
#ifndef QHTTPENGINE_QOBJECTHANDLERPRIVATE_H
#define QHTTPENGINE_QOBJECTHANDLERPRIVATE_H
#ifndef QHTTPENGINE_BASICAUTHMIDDLEWARE_P_H
#define QHTTPENGINE_BASICAUTHMIDDLEWARE_P_H
#include <QMap>
#include <QObject>
#include "QHttpEngine/qhttpsocket.h"
#include "QHttpEngine/qobjecthandler.h"
namespace QHttpEngine
{
class QObjectHandlerPrivate : public QObject
class BasicAuthMiddlewarePrivate : public QObject
{
Q_OBJECT
public:
explicit QObjectHandlerPrivate(QObjectHandler *handler);
explicit BasicAuthMiddlewarePrivate(QObject *parent, const QString &realm);
void invokeSlot(QHttpSocket *socket, int index);
QMap<QObject*, int> map;
private Q_SLOTS:
void onReadChannelFinished();
private:
QObjectHandler *const q;
const QString realm;
QMap<QString, QString> map;
};
#endif // QHTTPENGINE_QOBJECTHANDLERPRIVATE_H
}
#endif // QHTTPENGINE_BASICAUTHMIDDLEWARE_P_H

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -25,9 +25,14 @@
#include <QFileInfoList>
#include <QUrl>
#include "QHttpEngine/qfilesystemhandler.h"
#include "QHttpEngine/qiodevicecopier.h"
#include "qfilesystemhandler_p.h"
#include <qhttpengine/filesystemhandler.h>
#include <qhttpengine/qiodevicecopier.h>
#include <qhttpengine/range.h>
#include <qhttpengine/socket.h>
#include "filesystemhandler_p.h"
using namespace QHttpEngine;
// Template for listing directory contents
const QString ListTemplate =
@@ -46,12 +51,12 @@ const QString ListTemplate =
"</body>"
"</html>";
QFilesystemHandlerPrivate::QFilesystemHandlerPrivate(QFilesystemHandler *handler)
FilesystemHandlerPrivate::FilesystemHandlerPrivate(FilesystemHandler *handler)
: QObject(handler)
{
}
bool QFilesystemHandlerPrivate::absolutePath(const QString &path, QString &absolutePath)
bool FilesystemHandlerPrivate::absolutePath(const QString &path, QString &absolutePath)
{
// Resolve the path according to the document root
absolutePath = documentRoot.absoluteFilePath(path);
@@ -62,42 +67,74 @@ bool QFilesystemHandlerPrivate::absolutePath(const QString &path, QString &absol
return documentRoot.exists(absolutePath) && !documentRoot.relativeFilePath(path).startsWith("../");
}
QByteArray QFilesystemHandlerPrivate::mimeType(const QString &absolutePath)
QByteArray FilesystemHandlerPrivate::mimeType(const QString &absolutePath)
{
// Query the MIME database based on the filename and its contents
return database.mimeTypeForFile(absolutePath).name().toUtf8();
}
void QFilesystemHandlerPrivate::processFile(QHttpSocket *socket, const QString &absolutePath)
void FilesystemHandlerPrivate::processFile(Socket *socket, const QString &absolutePath)
{
// Attempt to open the file for reading
QFile *file = new QFile(absolutePath);
if(!file->open(QIODevice::ReadOnly)) {
if (!file->open(QIODevice::ReadOnly)) {
delete file;
socket->writeError(QHttpSocket::Forbidden);
socket->writeError(Socket::Forbidden);
return;
}
// Create a QIODeviceCopier to copy the file contents to the socket
QIODeviceCopier *copier = new QIODeviceCopier(file, socket);
connect(copier, SIGNAL(finished()), copier, SLOT(deleteLater()));
connect(copier, SIGNAL(finished()), file, SLOT(deleteLater()));
connect(copier, &QIODeviceCopier::finished, copier, &QIODeviceCopier::deleteLater);
connect(copier, &QIODeviceCopier::finished, file, &QFile::deleteLater);
connect(copier, &QIODeviceCopier::finished, [socket]() {
socket->close();
});
// Stop the copier if the socket is disconnected
connect(socket, &Socket::disconnected, copier, &QIODeviceCopier::stop);
qint64 fileSize = file->size();
// Checking for partial content request
QByteArray rangeHeader = socket->headers().value("Range");
Range range;
if (!rangeHeader.isEmpty() && rangeHeader.startsWith("bytes=")) {
// Skiping 'bytes=' - first 6 chars and spliting ranges by comma
QList<QByteArray> rangeList = rangeHeader.mid(6).split(',');
// Taking only first range, as multiple ranges require multipart
// reply support
range = Range(QString(rangeList.at(0)), fileSize);
}
// If range is valid, send partial content
if (range.isValid()) {
socket->setStatusCode(Socket::PartialContent);
socket->setHeader("Content-Length", QByteArray::number(range.length()));
socket->setHeader("Content-Range", QByteArray("bytes ") + range.contentRange().toLatin1());
copier->setRange(range.from(), range.to());
} else {
// If range is invalid or if it is not a partial content request,
// send full file
socket->setHeader("Content-Length", QByteArray::number(fileSize));
}
// Set the mimetype and content length
socket->setHeader("Content-Type", mimeType(absolutePath));
socket->setHeader("Content-Length", QByteArray::number(file->size()));
socket->writeHeaders();
// Start the copy
copier->start();
}
void QFilesystemHandlerPrivate::processDirectory(QHttpSocket *socket, const QString &path, const QString &absolutePath)
void FilesystemHandlerPrivate::processDirectory(Socket *socket, const QString &path, const QString &absolutePath)
{
// Add entries for each of the files
QString listing;
foreach(QFileInfo info, QDir(absolutePath).entryInfoList()) {
foreach (QFileInfo info, QDir(absolutePath).entryInfoList(QDir::NoFilter, QDir::Name | QDir::DirsFirst | QDir::IgnoreCase)) {
listing.append(QString("<li><a href=\"%1%2\">%1%2</a></li>")
.arg(info.fileName().toHtmlEscaped())
.arg(info.isDir() ? "/" : ""));
@@ -117,29 +154,29 @@ void QFilesystemHandlerPrivate::processDirectory(QHttpSocket *socket, const QStr
socket->close();
}
QFilesystemHandler::QFilesystemHandler(QObject *parent)
: QHttpHandler(parent),
d(new QFilesystemHandlerPrivate(this))
FilesystemHandler::FilesystemHandler(QObject *parent)
: Handler(parent),
d(new FilesystemHandlerPrivate(this))
{
}
QFilesystemHandler::QFilesystemHandler(const QString &documentRoot, QObject *parent)
: QHttpHandler(parent),
d(new QFilesystemHandlerPrivate(this))
FilesystemHandler::FilesystemHandler(const QString &documentRoot, QObject *parent)
: Handler(parent),
d(new FilesystemHandlerPrivate(this))
{
setDocumentRoot(documentRoot);
}
void QFilesystemHandler::setDocumentRoot(const QString &documentRoot)
void FilesystemHandler::setDocumentRoot(const QString &documentRoot)
{
d->documentRoot.setPath(documentRoot);
}
void QFilesystemHandler::process(QHttpSocket *socket, const QString &path)
void FilesystemHandler::process(Socket *socket, const QString &path)
{
// If a document root is not set, an error has occurred
if(d->documentRoot.path().isNull()) {
socket->writeError(QHttpSocket::InternalServerError);
if (d->documentRoot.path().isNull()) {
socket->writeError(Socket::InternalServerError);
return;
}
@@ -148,12 +185,12 @@ void QFilesystemHandler::process(QHttpSocket *socket, const QString &path)
// Attempt to retrieve the absolute path
QString absolutePath;
if(!d->absolutePath(decodedPath, absolutePath)) {
socket->writeError(QHttpSocket::NotFound);
if (!d->absolutePath(decodedPath, absolutePath)) {
socket->writeError(Socket::NotFound);
return;
}
if(QFileInfo(absolutePath).isDir()) {
if (QFileInfo(absolutePath).isDir()) {
d->processDirectory(socket, decodedPath, absolutePath);
} else {
d->processFile(socket, absolutePath);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -20,32 +20,37 @@
* IN THE SOFTWARE.
*/
#ifndef QHTTPENGINE_QFILESYSTEMHANDLERPRIVATE_H
#define QHTTPENGINE_QFILESYSTEMHANDLERPRIVATE_H
#ifndef QHTTPENGINE_FILESYSTEMHANDLER_P_H
#define QHTTPENGINE_FILESYSTEMHANDLER_P_H
#include <QDir>
#include <QMimeDatabase>
#include <QObject>
#include "QHttpEngine/qfilesystemhandler.h"
#include "QHttpEngine/qhttpsocket.h"
namespace QHttpEngine
{
class QFilesystemHandlerPrivate : public QObject
class FilesystemHandler;
class Socket;
class FilesystemHandlerPrivate : public QObject
{
Q_OBJECT
public:
QFilesystemHandlerPrivate(QFilesystemHandler *handler);
FilesystemHandlerPrivate(FilesystemHandler *handler);
bool absolutePath(const QString &path, QString &absolutePath);
QByteArray mimeType(const QString &path);
void processFile(QHttpSocket *socket, const QString &absolutePath);
void processDirectory(QHttpSocket *socket, const QString &path, const QString &absolutePath);
void processFile(Socket* socket, const QString &absolutePath);
void processDirectory(Socket* socket, const QString &path, const QString &absolutePath);
QDir documentRoot;
QMimeDatabase database;
};
#endif // QHTTPENGINE_QFILESYSTEMHANDLERPRIVATE_H
}
#endif // QHTTPENGINE_FILESYSTEMHANDLER_P_H

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -20,39 +20,55 @@
* IN THE SOFTWARE.
*/
#include <QHttpEngine/QHttpHandler>
#include <qhttpengine/handler.h>
#include <qhttpengine/middleware.h>
#include <qhttpengine/socket.h>
#include "qhttphandler_p.h"
#include "handler_p.h"
QHttpHandlerPrivate::QHttpHandlerPrivate(QHttpHandler *handler)
using namespace QHttpEngine;
HandlerPrivate::HandlerPrivate(Handler *handler)
: QObject(handler),
q(handler)
{
}
QHttpHandler::QHttpHandler(QObject *parent)
Handler::Handler(QObject *parent)
: QObject(parent),
d(new QHttpHandlerPrivate(this))
d(new HandlerPrivate(this))
{
}
void QHttpHandler::addRedirect(const QRegExp &pattern, const QString &path)
void Handler::addMiddleware(Middleware *middleware)
{
d->middleware.append(middleware);
}
void Handler::addRedirect(const QRegExp &pattern, const QString &path)
{
d->redirects.append(Redirect(pattern, path));
}
void QHttpHandler::addSubHandler(const QRegExp &pattern, QHttpHandler *handler)
void Handler::addSubHandler(const QRegExp &pattern, Handler *handler)
{
d->subHandlers.append(SubHandler(pattern, handler));
}
void QHttpHandler::route(QHttpSocket *socket, const QString &path)
void Handler::route(Socket *socket, const QString &path)
{
// Run through each of the middleware
foreach (Middleware *middleware, d->middleware) {
if (!middleware->process(socket)) {
return;
}
}
// Check each of the redirects for a match
foreach(Redirect redirect, d->redirects) {
if(redirect.first.indexIn(path) != -1) {
foreach (Redirect redirect, d->redirects) {
if (redirect.first.indexIn(path) != -1) {
QString newPath = redirect.second;
foreach(QString replacement, redirect.first.capturedTexts().mid(1)) {
foreach (QString replacement, redirect.first.capturedTexts().mid(1)) {
newPath = newPath.arg(replacement);
}
socket->writeRedirect(newPath.toUtf8());
@@ -61,8 +77,8 @@ void QHttpHandler::route(QHttpSocket *socket, const QString &path)
}
// Check each of the sub-handlers for a match
foreach(SubHandler subHandler, d->subHandlers) {
if(subHandler.first.indexIn(path) != -1) {
foreach (SubHandler subHandler, d->subHandlers) {
if (subHandler.first.indexIn(path) != -1) {
subHandler.second->route(socket, path.mid(subHandler.first.matchedLength()));
return;
}
@@ -72,8 +88,8 @@ void QHttpHandler::route(QHttpSocket *socket, const QString &path)
process(socket, path);
}
void QHttpHandler::process(QHttpSocket *socket, const QString &)
void Handler::process(Socket *socket, const QString &)
{
// The default response is simply a 404 error
socket->writeError(QHttpSocket::NotFound);
socket->writeError(Socket::NotFound);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -20,33 +20,39 @@
* IN THE SOFTWARE.
*/
#ifndef QHTTPENGINE_QHTTPHANDLERPRIVATE_H
#define QHTTPENGINE_QHTTPHANDLERPRIVATE_H
#ifndef QHTTPENGINE_HANDLER_P_H
#define QHTTPENGINE_HANDLER_P_H
#include <QList>
#include <QObject>
#include <QPair>
#include <QRegExp>
#include "QHttpEngine/qhttphandler.h"
#include <qhttpengine/handler.h>
namespace QHttpEngine
{
typedef QPair<QRegExp, QString> Redirect;
typedef QPair<QRegExp, QHttpHandler*> SubHandler;
typedef QPair<QRegExp, Handler*> SubHandler;
class QHttpHandlerPrivate : public QObject
class HandlerPrivate : public QObject
{
Q_OBJECT
public:
explicit QHttpHandlerPrivate(QHttpHandler *handler);
explicit HandlerPrivate(Handler *handler);
QList<Redirect> redirects;
QList<SubHandler> subHandlers;
QList<Middleware*> middleware;
private:
QHttpHandler *const q;
Handler *const q;
};
#endif // QHTTPENGINE_QHTTPHANDLERPRIVATE_H
}
#endif // QHTTPENGINE_HANDLER_P_H

View File

@@ -0,0 +1,91 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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.
*/
#include <QJsonDocument>
#include <QJsonObject>
#include <QUuid>
#include <qhttpengine/localauthmiddleware.h>
#include <qhttpengine/socket.h>
#include "localauthmiddleware_p.h"
using namespace QHttpEngine;
LocalAuthMiddlewarePrivate::LocalAuthMiddlewarePrivate(QObject *parent)
: QObject(parent),
tokenHeader("X-Auth-Token"),
token(QUuid::createUuid().toString())
{
updateFile();
}
LocalAuthMiddlewarePrivate::~LocalAuthMiddlewarePrivate()
{
file.remove();
}
void LocalAuthMiddlewarePrivate::updateFile()
{
if (file.open()) {
file.write(QJsonDocument(QJsonObject::fromVariantMap(data)).toJson());
file.close();
}
}
LocalAuthMiddleware::LocalAuthMiddleware(QObject *parent)
: Middleware(parent),
d(new LocalAuthMiddlewarePrivate(this))
{
}
bool LocalAuthMiddleware::exists() const
{
return d->file.exists();
}
QString LocalAuthMiddleware::filename() const
{
return d->file.fileName();
}
void LocalAuthMiddleware::setData(const QVariantMap &data)
{
d->data = data;
d->data.insert("token", d->token);
d->updateFile();
}
void LocalAuthMiddleware::setHeaderName(const QByteArray &name)
{
d->tokenHeader = name;
}
bool LocalAuthMiddleware::process(Socket *socket)
{
if (socket->headers().value(d->tokenHeader) != d->token) {
socket->writeError(Socket::Forbidden);
return false;
}
return true;
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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 QHTTPENGINE_LOCALAUTHMIDDLEWARE_P_H
#define QHTTPENGINE_LOCALAUTHMIDDLEWARE_P_H
#include <QObject>
#include <QVariantMap>
#include <qhttpengine/localfile.h>
namespace QHttpEngine
{
class LocalAuthMiddlewarePrivate : public QObject
{
Q_OBJECT
public:
explicit LocalAuthMiddlewarePrivate(QObject *parent);
virtual ~LocalAuthMiddlewarePrivate();
void updateFile();
LocalFile file;
QVariantMap data;
QByteArray tokenHeader;
QString token;
};
}
#endif // QHTTPENGINE_LOCALAUTHMIDDLEWARE_P_H

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -25,15 +25,18 @@
#if defined(Q_OS_UNIX)
# include <sys/stat.h>
#elif defined(Q_OS_WIN)
#elif defined(Q_OS_WIN32)
# include <aclapi.h>
# include <fileapi.h>
# include <windows.h>
#endif
#include "QHttpEngine/qlocalfile.h"
#include "qlocalfile_p.h"
#include <qhttpengine/localfile.h>
QLocalFilePrivate::QLocalFilePrivate(QLocalFile *localFile)
#include "localfile_p.h"
using namespace QHttpEngine;
LocalFilePrivate::LocalFilePrivate(LocalFile *localFile)
: QObject(localFile),
q(localFile)
{
@@ -42,51 +45,34 @@ QLocalFilePrivate::QLocalFilePrivate(QLocalFile *localFile)
q->setFileName(QDir::home().absoluteFilePath("." + QCoreApplication::applicationName()));
}
bool QLocalFilePrivate::setPermission()
bool LocalFilePrivate::setPermission()
{
#if defined(Q_OS_UNIX)
return chmod(q->fileName().toUtf8().constData(), S_IRUSR | S_IWUSR) == 0;
#elif defined(Q_OS_WIN)
#elif defined(Q_OS_WIN32)
// Windows uses ACLs to control file access - each file contains an ACL
// which consists of one or more ACEs (access control entries) - so the
// ACL for the file must contain only a single ACE, granting access to the
// file owner (the current user)
// Retrieve the owner SID for the file
PSID pSID;
PSECURITY_DESCRIPTOR pSD;
if(GetNamedSecurityInfoW((LPCWSTR)q->fileName().utf16(),
SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION,
&pSID,
NULL,
NULL,
NULL,
&pSD) != ERROR_SUCCESS) {
return false;
}
EXPLICIT_ACCESS_W ea;
ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));
ea.grfAccessPermissions = STANDARD_RIGHTS_REQUIRED;
ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS_W));
ea.grfAccessPermissions = GENERIC_ALL;
ea.grfAccessMode = GRANT_ACCESS;
ea.grfInheritance = NO_INHERITANCE;
ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea.Trustee.ptstrName = (LPWSTR)pSID;
ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
ea.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
ea.Trustee.ptstrName = L"CURRENT_USER";
// Create a new ACL with a single access control entry
PACL pACL;
if(SetEntriesInAclW(1, &ea, NULL, &pACL) != ERROR_SUCCESS) {
LocalFree(pSD);
if (SetEntriesInAclW(1, &ea, NULL, &pACL) != ERROR_SUCCESS) {
return false;
}
LocalFree(pSD);
// Apply the ACL to the file
if(SetNamedSecurityInfoW((LPWSTR)q->fileName().utf16(),
if (SetNamedSecurityInfoW((LPWSTR)q->fileName().utf16(),
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION,
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
NULL,
NULL,
pACL,
@@ -103,12 +89,12 @@ bool QLocalFilePrivate::setPermission()
#endif
}
bool QLocalFilePrivate::setHidden()
bool LocalFilePrivate::setHidden()
{
#if defined(Q_OS_UNIX)
// On Unix, anything beginning with a "." is hidden
return true;
#elif defined(Q_OS_WIN)
#elif defined(Q_OS_WIN32)
return SetFileAttributesW((LPCWSTR)q->fileName().utf16(), FILE_ATTRIBUTE_HIDDEN) != 0;
#else
// Unsupported platform, so setHidden() must fail
@@ -116,13 +102,13 @@ bool QLocalFilePrivate::setHidden()
#endif
}
QLocalFile::QLocalFile(QObject *parent)
LocalFile::LocalFile(QObject *parent)
: QFile(parent),
d(new QLocalFilePrivate(this))
d(new LocalFilePrivate(this))
{
}
bool QLocalFile::open()
bool LocalFile::open()
{
return QFile::open(QIODevice::WriteOnly) && d->setPermission() && d->setHidden();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -20,27 +20,32 @@
* IN THE SOFTWARE.
*/
#ifndef QHTTPENGINE_QLOCALFILEPRIVATE_H
#define QHTTPENGINE_QLOCALFILEPRIVATE_H
#ifndef QHTTPENGINE_LOCALFILE_P_H
#define QHTTPENGINE_LOCALFILE_P_H
#include <QObject>
#include "QHttpEngine/qlocalfile.h"
namespace QHttpEngine
{
class QLocalFilePrivate : public QObject
class LocalFile;
class LocalFilePrivate : public QObject
{
Q_OBJECT
public:
explicit QLocalFilePrivate(QLocalFile *localFile);
explicit LocalFilePrivate(LocalFile *localFile);
bool setPermission();
bool setHidden();
private:
QLocalFile *const q;
LocalFile *const q;
};
#endif // QHTTPENGINE_QLOCALFILEPRIVATE_H
}
#endif // QHTTPENGINE_LOCALFILE_P_H

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -20,18 +20,22 @@
* IN THE SOFTWARE.
*/
#include <QByteArray>
#include <QPair>
#include <QUrl>
#include <QUrlQuery>
#include "QHttpEngine/qhttpparser.h"
#include <qhttpengine/parser.h>
void QHttpParser::split(const QByteArray &data, const QByteArray &delim, int maxSplit, QList<QByteArray> &parts)
using namespace QHttpEngine;
void Parser::split(const QByteArray &data, const QByteArray &delim, int maxSplit, QByteArrayList &parts)
{
int index = 0;
for(int i = 0; !maxSplit || i < maxSplit; ++i) {
for (int i = 0; !maxSplit || i < maxSplit; ++i) {
int nextIndex = data.indexOf(delim, index);
if(nextIndex == -1) {
if (nextIndex == -1) {
break;
}
@@ -43,15 +47,31 @@ void QHttpParser::split(const QByteArray &data, const QByteArray &delim, int max
parts.append(data.mid(index));
}
bool QHttpParser::parseHeaderList(const QList<QByteArray> &lines, QHttpHeaderMap &headers)
bool Parser::parsePath(const QByteArray &rawPath, QString &path, Socket::QueryStringMap &queryString)
{
foreach(const QByteArray &line, lines) {
QUrl url(rawPath);
if (!url.isValid()) {
return false;
}
path = url.path();
QPair<QString, QString> pair;
foreach (pair, QUrlQuery(url.query()).queryItems()) {
queryString.insert(pair.first, pair.second);
}
return true;
}
bool Parser::parseHeaderList(const QList<QByteArray> &lines, Socket::HeaderMap &headers)
{
foreach (const QByteArray &line, lines) {
QList<QByteArray> parts;
split(line, ":", 1, parts);
// Ensure that the delimiter (":") was encountered at least once
if(parts.count() != 2) {
if (parts.count() != 2) {
return false;
}
@@ -62,7 +82,7 @@ bool QHttpParser::parseHeaderList(const QList<QByteArray> &lines, QHttpHeaderMap
return true;
}
bool QHttpParser::parseHeaders(const QByteArray &data, QList<QByteArray> &parts, QHttpHeaderMap &headers)
bool Parser::parseHeaders(const QByteArray &data, QList<QByteArray> &parts, Socket::HeaderMap &headers)
{
// Split the data into individual lines
QList<QByteArray> lines;
@@ -70,35 +90,54 @@ bool QHttpParser::parseHeaders(const QByteArray &data, QList<QByteArray> &parts,
// Split the first line into a maximum of three parts
split(lines.takeFirst(), " ", 2, parts);
if(parts.count() != 3) {
if (parts.count() != 3) {
return false;
}
return parseHeaderList(lines, headers);
}
bool QHttpParser::parseRequestHeaders(const QByteArray &data, QByteArray &method, QByteArray &path, QHttpHeaderMap &headers)
bool Parser::parseRequestHeaders(const QByteArray &data, Socket::Method &method, QByteArray &path, Socket::HeaderMap &headers)
{
QList<QByteArray> parts;
if(!parseHeaders(data, parts, headers)) {
if (!parseHeaders(data, parts, headers)) {
return false;
}
// Only HTTP/1.x versions are supported for now
if(parts[2] != "HTTP/1.0" && parts[2] != "HTTP/1.1") {
if (parts[2] != "HTTP/1.0" && parts[2] != "HTTP/1.1") {
return false;
}
if (parts[0] == "OPTIONS") {
method = Socket::OPTIONS;
} else if (parts[0] == "GET") {
method = Socket::GET;
} else if (parts[0] == "HEAD") {
method = Socket::HEAD;
} else if (parts[0] == "POST") {
method = Socket::POST;
} else if (parts[0] == "PUT") {
method = Socket::PUT;
} else if (parts[0] == "DELETE") {
method = Socket::DELETE;
} else if (parts[0] == "TRACE") {
method = Socket::TRACE;
} else if (parts[0] == "CONNECT") {
method = Socket::CONNECT;
} else {
return false;
}
method = parts[0];
path = parts[1];
return true;
}
bool QHttpParser::parseResponseHeaders(const QByteArray &data, int &statusCode, QByteArray &statusReason, QHttpHeaderMap &headers)
bool Parser::parseResponseHeaders(const QByteArray &data, int &statusCode, QByteArray &statusReason, Socket::HeaderMap &headers)
{
QList<QByteArray> parts;
if(!parseHeaders(data, parts, headers)) {
if (!parseHeaders(data, parts, headers)) {
return false;
}

50
src/src/proxyhandler.cpp Normal file
View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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.
*/
#include <qhttpengine/proxyhandler.h>
#include "proxyhandler_p.h"
#include "proxysocket.h"
using namespace QHttpEngine;
ProxyHandlerPrivate::ProxyHandlerPrivate(QObject *parent, const QHostAddress &address, quint16 port)
: QObject(parent),
address(address),
port(port)
{
}
ProxyHandler::ProxyHandler(const QHostAddress &address, quint16 port, QObject *parent)
: Handler(parent),
d(new ProxyHandlerPrivate(this, address, port))
{
}
void ProxyHandler::process(Socket *socket, const QString &path)
{
// Parent the socket to the proxy
socket->setParent(this);
// Create a new proxy socket
new ProxySocket(socket, path, d->address, d->port);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -20,33 +20,29 @@
* IN THE SOFTWARE.
*/
#ifndef QHTTPENGINE_QHTTPSERVERPRIVATE_H
#define QHTTPENGINE_QHTTPSERVERPRIVATE_H
#ifndef QHTTPENGINE_PROXYHANDLER_P_H
#define QHTTPENGINE_PROXYHANDLER_P_H
#include <QHostAddress>
#include <QObject>
#include <QTcpServer>
#include "QHttpEngine/qhttphandler.h"
#include "QHttpEngine/qhttpserver.h"
#include <qhttpengine/socket.h>
class QHttpServerPrivate : public QObject
namespace QHttpEngine
{
class ProxyHandlerPrivate : public QObject
{
Q_OBJECT
public:
QHttpServerPrivate(QHttpServer *httpServer);
ProxyHandlerPrivate(QObject *parent, const QHostAddress &address, quint16 port);
QHttpHandler *handler;
private Q_SLOTS:
void onIncomingConnection();
void onHeadersParsed();
private:
QHttpServer *const q;
QHostAddress address;
quint16 port;
};
#endif // QHTTPENGINE_QHTTPSERVERPRIVATE_H
}
#endif // QHTTPENGINE_PROXYHANDLER_P_H

160
src/src/proxysocket.cpp Normal file
View File

@@ -0,0 +1,160 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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.
*/
#include <qhttpengine/parser.h>
#include "proxysocket.h"
using namespace QHttpEngine;
ProxySocket::ProxySocket(Socket *socket, const QString &path, const QHostAddress &address, quint16 port)
: QObject(socket),
mDownstreamSocket(socket),
mPath(path),
mHeadersParsed(false),
mHeadersWritten(false)
{
connect(mDownstreamSocket, &Socket::readyRead, this, &ProxySocket::onDownstreamReadyRead);
connect(mDownstreamSocket, &Socket::disconnected, this, &ProxySocket::onDownstreamDisconnected);
connect(&mUpstreamSocket, &QTcpSocket::connected, this, &ProxySocket::onUpstreamConnected);
connect(&mUpstreamSocket, &QTcpSocket::readyRead, this, &ProxySocket::onUpstreamReadyRead);
connect(
&mUpstreamSocket,
static_cast<void(QAbstractSocket::*)(QAbstractSocket::SocketError)>(&QAbstractSocket::error),
this,
&ProxySocket::onUpstreamError
);
mUpstreamSocket.connectToHost(address, port);
}
void ProxySocket::onDownstreamReadyRead()
{
if (mHeadersWritten) {
mUpstreamSocket.write(mDownstreamSocket->readAll());
} else {
mUpstreamWrite.append(mDownstreamSocket->readAll());
}
}
void ProxySocket::onDownstreamDisconnected()
{
mUpstreamSocket.disconnectFromHost();
}
void ProxySocket::onUpstreamConnected()
{
// Write the status line using the stripped path from the handler
mUpstreamSocket.write(
QString("%1 /%2 HTTP/1.1\r\n")
.arg(methodToString(mDownstreamSocket->method()))
.arg(mPath)
.toUtf8()
);
// Use the existing headers but insert proxy-related ones
Socket::HeaderMap headers = mDownstreamSocket->headers();
QByteArray peerIP = mDownstreamSocket->peerAddress().toString().toUtf8();
QByteArray origFwd = headers.value("X-Forwarded-For");
if (origFwd.isNull()) {
headers.insert("X-Forwarded-For", peerIP);
} else {
headers.insert("X-Forwarded-For", origFwd + ", " + peerIP);
}
if (!headers.contains("X-Real-IP")) {
headers.insert("X-Real-IP", peerIP);
}
// Write the headers to the socket with the terminating CRLF
for (auto i = headers.constBegin(); i != headers.constEnd(); ++i) {
mUpstreamSocket.write(i.key() + ": " + i.value() + "\r\n");
}
mUpstreamSocket.write("\r\n");
mHeadersWritten = true;
// If there is any data buffered for writing, write it
if (mUpstreamWrite.size()) {
mUpstreamSocket.write(mUpstreamWrite);
mUpstreamWrite.clear();
}
}
void ProxySocket::onUpstreamReadyRead()
{
// If the headers have not yet been parsed, then check to see if the end
// has been reached yet; if they have, just dump data
if (!mHeadersParsed) {
// Add to the buffer and check to see if the end was reached
mUpstreamRead.append(mUpstreamSocket.readAll());
int index = mUpstreamRead.indexOf("\r\n\r\n");
if (index != -1) {
// Parse the headers
int statusCode;
QByteArray statusReason;
Socket::HeaderMap headers;
if (!Parser::parseResponseHeaders(mUpstreamRead.left(index), statusCode, statusReason, headers)) {
mDownstreamSocket->writeError(Socket::BadGateway);
return;
}
// Dump the headers back downstream
mDownstreamSocket->setStatusCode(statusCode, statusReason);
mDownstreamSocket->setHeaders(headers);
mDownstreamSocket->writeHeaders();
mDownstreamSocket->write(mUpstreamRead.mid(index + 4));
// Remember that headers were parsed and empty the buffer
mHeadersParsed = true;
mUpstreamRead.clear();
}
} else {
mDownstreamSocket->write(mUpstreamSocket.readAll());
}
}
void ProxySocket::onUpstreamError(QAbstractSocket::SocketError socketError)
{
if (mHeadersParsed) {
mDownstreamSocket->close();
} else {
mDownstreamSocket->writeError(Socket::BadGateway);
}
}
QString ProxySocket::methodToString(Socket::Method method) const
{
switch (method) {
case Socket::OPTIONS: return "OPTIONS";
case Socket::GET: return "GET";
case Socket::HEAD: return "HEAD";
case Socket::POST: return "POST";
case Socket::PUT: return "PUT";
case Socket::DELETE: return "DELETE";
case Socket::TRACE: return "TRACE";
case Socket::CONNECT: return "CONNECT";
default: return QString();
}
}

71
src/src/proxysocket.h Normal file
View File

@@ -0,0 +1,71 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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 QHTTPENGINE_PROXYSOCKET_H
#define QHTTPENGINE_PROXYSOCKET_H
#include <QHostAddress>
#include <QObject>
#include <QTcpSocket>
#include <qhttpengine/socket.h>
/**
* @brief HTTP socket for connecting to a proxy
*
* The proxy socket manages the two socket connections - one for downstream
* (the client's connection to the server) and one for upstream (the server's
* connection to the upstream proxy).
*/
class ProxySocket: public QObject
{
Q_OBJECT
public:
explicit ProxySocket(QHttpEngine::Socket *socket, const QString &path, const QHostAddress &address, quint16 port);
private Q_SLOTS:
void onDownstreamReadyRead();
void onDownstreamDisconnected();
void onUpstreamConnected();
void onUpstreamReadyRead();
void onUpstreamError(QTcpSocket::SocketError socketError);
private:
QString methodToString(QHttpEngine::Socket::Method method) const;
QHttpEngine::Socket *mDownstreamSocket;
QTcpSocket mUpstreamSocket;
QString mPath;
bool mHeadersParsed;
bool mHeadersWritten;
QByteArray mUpstreamRead;
QByteArray mUpstreamWrite;
};
#endif // QHTTPENGINE_PROXYSOCKET_H

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -20,11 +20,15 @@
* IN THE SOFTWARE.
*/
#include <QIODevice>
#include <QTimer>
#include "QHttpEngine/qiodevicecopier.h"
#include <qhttpengine/qiodevicecopier.h>
#include "qiodevicecopier_p.h"
using namespace QHttpEngine;
// Default value for the bufferSize property
const qint64 DefaultBufferSize = 65536;
@@ -33,13 +37,15 @@ QIODeviceCopierPrivate::QIODeviceCopierPrivate(QIODeviceCopier *copier, QIODevic
q(copier),
src(srcDevice),
dest(destDevice),
bufferSize(DefaultBufferSize)
bufferSize(DefaultBufferSize),
rangeFrom(0),
rangeTo(-1)
{
}
void QIODeviceCopierPrivate::onReadyRead()
{
if(dest->write(src->readAll()) == -1) {
if (dest->write(src->readAll()) == -1) {
Q_EMIT q->error(dest->errorString());
src->close();
}
@@ -48,7 +54,7 @@ void QIODeviceCopierPrivate::onReadyRead()
void QIODeviceCopierPrivate::onReadChannelFinished()
{
// Read any data that remains and signal the end of the operation
if(src->bytesAvailable()) {
if (src->bytesAvailable()) {
onReadyRead();
}
@@ -63,26 +69,32 @@ void QIODeviceCopierPrivate::nextBlock()
qint64 dataRead = src->read(data.data(), bufferSize);
// If an error occurred during the read, emit an error
if(dataRead == -1) {
if (dataRead == -1) {
Q_EMIT q->error(src->errorString());
Q_EMIT q->finished();
return;
}
// If range is specified (rangeTo >= 0), check if end of range is reached;
// if it is, send only part from buffer truncated by range end
if (rangeTo != -1 && src->pos() > rangeTo) {
dataRead -= src->pos() - rangeTo - 1;
}
// Write the data to the destination device
if(dest->write(data.constData(), dataRead) == -1) {
if (dest->write(data.constData(), dataRead) == -1) {
Q_EMIT q->error(dest->errorString());
Q_EMIT q->finished();
return;
}
// Check if the end of the device has been reached - if so,
// emit the finished signal and if not, continue to read
// data at the next iteration of the event loop
if(src->atEnd()) {
// Check if the end of the device has been reached or if the end of
// the requested range is reached - if so, emit the finished signal and
// if not, continue to read data at the next iteration of the event loop
if (src->atEnd() || (rangeTo != -1 && src->pos() > rangeTo)) {
Q_EMIT q->finished();
} else {
QTimer::singleShot(0, this, SLOT(nextBlock()));
QTimer::singleShot(0, this, &QIODeviceCopierPrivate::nextBlock);
}
}
@@ -90,8 +102,8 @@ QIODeviceCopier::QIODeviceCopier(QIODevice *src, QIODevice *dest, QObject *paren
: QObject(parent),
d(new QIODeviceCopierPrivate(this, src, dest))
{
connect(src, SIGNAL(destroyed()), this, SLOT(stop()));
connect(dest, SIGNAL(destroyed()), this, SLOT(stop()));
connect(src, &QIODevice::destroyed, this, &QIODeviceCopier::stop);
connect(dest, &QIODevice::destroyed, this, &QIODeviceCopier::stop);
}
void QIODeviceCopier::setBufferSize(qint64 size)
@@ -99,41 +111,58 @@ void QIODeviceCopier::setBufferSize(qint64 size)
d->bufferSize = size;
}
void QIODeviceCopier::setRange(qint64 from, qint64 to)
{
d->rangeFrom = from;
d->rangeTo = to;
}
void QIODeviceCopier::start()
{
if(!d->src->isOpen()) {
if(!d->src->open(QIODevice::ReadOnly)) {
if (!d->src->isOpen()) {
if (!d->src->open(QIODevice::ReadOnly)) {
Q_EMIT error(tr("Unable to open source device for reading"));
Q_EMIT finished();
return;
}
}
if(!d->dest->isOpen()) {
if(!d->dest->open(QIODevice::WriteOnly)) {
if (!d->dest->isOpen()) {
if (!d->dest->open(QIODevice::WriteOnly)) {
Q_EMIT error(tr("Unable to open destination device for writing"));
Q_EMIT finished();
return;
}
}
// If range is set and d->src is not sequential, seek to starting position
if (d->rangeFrom > 0 && !d->src->isSequential()) {
if (!d->src->seek(d->rangeFrom)) {
Q_EMIT error(tr("Unable to seek source device for specified range"));
Q_EMIT finished();
return;
}
}
// These signals cannot be connected in the constructor since they may
// begin firing before the start() method is called
// readyRead() and readChannelFinished() are only emitted for sequential
// devices - for other types of devices, it is necessary to check atEnd()
// in order to determine whether the end of the device has been reached
connect(d->src, SIGNAL(readyRead()), d, SLOT(onReadyRead()));
connect(d->src, SIGNAL(readChannelFinished()), d, SLOT(onReadChannelFinished()));
connect(d->src, &QIODevice::readyRead, d, &QIODeviceCopierPrivate::onReadyRead);
connect(d->src, &QIODevice::readChannelFinished, d, &QIODeviceCopierPrivate::onReadChannelFinished);
// The first read from the device needs to be triggered
QTimer::singleShot(0, d, d->src->isSequential() ? SLOT(onReadyRead()) : SLOT(nextBlock()));
QTimer::singleShot(0, d, d->src->isSequential() ?
&QIODeviceCopierPrivate::onReadyRead :
&QIODeviceCopierPrivate::nextBlock);
}
void QIODeviceCopier::stop()
{
disconnect(d->src, SIGNAL(readyRead()), d, SLOT(onReadyRead()));
disconnect(d->src, SIGNAL(readChannelFinished()), d, SLOT(onReadChannelFinished()));
disconnect(d->src, &QIODevice::readyRead, d, &QIODeviceCopierPrivate::onReadyRead);
disconnect(d->src, &QIODevice::readChannelFinished, d, &QIODeviceCopierPrivate::onReadChannelFinished);
Q_EMIT finished();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -20,13 +20,17 @@
* IN THE SOFTWARE.
*/
#ifndef QHTTPENGINE_QIODEVICECOPIERPRIVATE_H
#define QHTTPENGINE_QIODEVICECOPIERPRIVATE_H
#ifndef QHTTPENGINE_QIODEVICECOPIER_P_H
#define QHTTPENGINE_QIODEVICECOPIER_P_H
#include <QIODevice>
#include <QObject>
#include "QHttpEngine/qiodevicecopier.h"
class QIODevice;
namespace QHttpEngine
{
class QIODeviceCopier;
class QIODeviceCopierPrivate : public QObject
{
@@ -41,7 +45,10 @@ public:
qint64 bufferSize;
private Q_SLOTS:
qint64 rangeFrom;
qint64 rangeTo;
public Q_SLOTS:
void onReadyRead();
void onReadChannelFinished();
@@ -53,4 +60,6 @@ private:
QIODeviceCopier *const q;
};
#endif // QHTTPENGINE_QIODEVICECOPIERPRIVATE_H
}
#endif // QHTTPENGINE_QIODEVICECOPIER_P_H

110
src/src/qobjecthandler.cpp Normal file
View File

@@ -0,0 +1,110 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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.
*/
#include <QGenericArgument>
#include <QMetaMethod>
#include <qhttpengine/qobjecthandler.h>
#include <qhttpengine/socket.h>
#include "qobjecthandler_p.h"
using namespace QHttpEngine;
QObjectHandlerPrivate::QObjectHandlerPrivate(QObjectHandler *handler)
: QObject(handler),
q(handler)
{
}
QObjectHandler::QObjectHandler(QObject *parent)
: Handler(parent),
d(new QObjectHandlerPrivate(this))
{
}
void QObjectHandlerPrivate::invokeSlot(Socket *socket, Method m)
{
// Invoke the slot
if (m.oldSlot) {
// Obtain the slot index
int index = m.receiver->metaObject()->indexOfSlot(m.slot.method + 1);
if (index == -1) {
socket->writeError(Socket::InternalServerError);
return;
}
QMetaMethod method = m.receiver->metaObject()->method(index);
// Ensure the parameter is correct
QList<QByteArray> params = method.parameterTypes();
if (params.count() != 1 || params.at(0) != "QHttpEngine::Socket*") {
socket->writeError(Socket::InternalServerError);
return;
}
// Invoke the method
if (!m.receiver->metaObject()->method(index).invoke(
m.receiver, Q_ARG(Socket*, socket))) {
socket->writeError(Socket::InternalServerError);
return;
}
} else {
void *args[] = {
Q_NULLPTR,
&socket
};
m.slot.slotObj->call(m.receiver, args);
}
}
void QObjectHandler::process(Socket *socket, const QString &path)
{
// Ensure the method has been registered
if (!d->map.contains(path)) {
socket->writeError(Socket::NotFound);
return;
}
QObjectHandlerPrivate::Method m = d->map.value(path);
// If the slot requires all data to be received, check to see if this is
// already the case, otherwise, wait until the rest of it arrives
if (!m.readAll || socket->bytesAvailable() >= socket->contentLength()) {
d->invokeSlot(socket, m);
} else {
connect(socket, &Socket::readChannelFinished, [this, socket, m]() {
d->invokeSlot(socket, m);
});
}
}
void QObjectHandler::registerMethod(const QString &name, QObject *receiver, const char *method, bool readAll)
{
d->map.insert(name, QObjectHandlerPrivate::Method(receiver, method, readAll));
}
void QObjectHandler::registerMethodImpl(const QString &name, QObject *receiver, QtPrivate::QSlotObjectBase *slotObj, bool readAll)
{
d->map.insert(name, QObjectHandlerPrivate::Method(receiver, slotObj, readAll));
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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 QHTTPENGINE_QOBJECTHANDLER_P_H
#define QHTTPENGINE_QOBJECTHANDLER_P_H
#include <QMap>
#include <QObject>
namespace QHttpEngine
{
class Socket;
class QObjectHandler;
class QObjectHandlerPrivate : public QObject
{
Q_OBJECT
public:
explicit QObjectHandlerPrivate(QObjectHandler *handler);
// In order to invoke the slot, a "pointer" to it needs to be stored in a
// map that lets us look up information by method name
class Method {
public:
Method() {}
Method(QObject *receiver, const char *method, bool readAll)
: receiver(receiver), oldSlot(true), slot(method), readAll(readAll) {}
Method(QObject *receiver, QtPrivate::QSlotObjectBase *slotObj, bool readAll)
: receiver(receiver), oldSlot(false), slot(slotObj), readAll(readAll) {}
QObject *receiver;
bool oldSlot;
union slot{
slot() {}
slot(const char *method) : method(method) {}
slot(QtPrivate::QSlotObjectBase *slotObj) : slotObj(slotObj) {}
const char *method;
QtPrivate::QSlotObjectBase *slotObj;
} slot;
bool readAll;
};
void invokeSlot(Socket*socket, Method m);
QMap<QString, Method> map;
private:
QObjectHandler *const q;
};
}
#endif // QHTTPENGINE_QOBJECTHANDLER_P_H

284
src/src/range.cpp Normal file
View File

@@ -0,0 +1,284 @@
/*
* Copyright (c) 2017 Aleksei Ermakov
*
* 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.
*/
#include <QRegExp>
#include <qhttpengine/range.h>
#include "range_p.h"
using namespace QHttpEngine;
RangePrivate::RangePrivate(Range *range)
: q(range)
{
}
Range::Range()
: d(new RangePrivate(this))
{
d->from = 1;
d->to = 0;
d->dataSize = -1;
}
Range::Range(const QString &range, qint64 dataSize)
: d(new RangePrivate(this))
{
QRegExp regExp("^(\\d*)-(\\d*)$");
int from = 0, to = -1;
if (regExp.indexIn(range.trimmed()) != -1) {
QString fromStr = regExp.cap(1);
QString toStr = regExp.cap(2);
// If both strings are empty - range is invalid. Setting to out of
// bounds range and returning.
if (fromStr.isEmpty() && toStr.isEmpty()) {
d->from = 1;
d->to = 0;
d->dataSize = -1;
return;
}
bool okFrom = true, okTo = true;
if (!fromStr.isEmpty()) {
from = fromStr.toInt(&okFrom);
}
if (!toStr.isEmpty()) {
to = toStr.toInt(&okTo);
}
// If failed to parse value - set to invalid range and return.
if (!okFrom) {
d->from = 1;
d->to = 0;
d->dataSize = -1;
return;
}
if (!okTo) {
d->from = 1;
d->to = 0;
d->dataSize = -1;
return;
}
// In case of 'last N bytes' range (Ex.: "Range: bytes=-500"),
// set from to -to and to to -1
if (fromStr.isEmpty()) {
from = -to;
to = -1;
}
} else { // If regexp didn't match - set to invalid range and return.
d->from = 1;
d->to = 0;
d->dataSize = -1;
return;
}
d->from = from;
d->to = to;
d->dataSize = dataSize;
}
Range::Range(qint64 from, qint64 to, qint64 dataSize)
: d(new RangePrivate(this))
{
d->from = from;
d->to = to < 0 ? -1 : to;
d->dataSize = dataSize < 0 ? -1 : dataSize;
}
Range::Range(const Range &other, qint64 dataSize)
: d(new RangePrivate(this))
{
d->from = other.d->from;
d->to = other.d->to;
d->dataSize = dataSize;
}
Range::~Range()
{
delete d;
}
Range& Range::operator=(const Range &other)
{
if (&other != this) {
d->from = other.d->from;
d->to = other.d->to;
d->dataSize = other.d->dataSize;
}
return *this;
}
qint64 Range::from() const
{
// Last N bytes requested
if (d->from < 0 && d->dataSize != -1) {
// Check if data is smaller then requested range
if (- d->from >= d->dataSize) {
return 0;
}
return d->dataSize + d->from;
}
// Check if d->from is bigger than d->to or d->dataSize
if ((d->from > d->to && d->to != -1) ||
(d->from >= d->dataSize && d->dataSize != -1)) {
return 0;
}
return d->from;
}
qint64 Range::to() const
{
// Last N bytes requested
if (d->from < 0 && d->dataSize != -1) {
return d->dataSize - 1;
}
// Skip first N bytes requested
if (d->from > 0 && d->to == -1 && d->dataSize != -1) {
return d->dataSize - 1;
}
// Check if d->from is bigger then d->to
if (d->from > d->to && d->to != -1) {
return d->from;
}
// When d->to overshoots dataSize
if ((d->to >= d->dataSize || d->to == -1) && d->dataSize != -1) {
return d->dataSize - 1;
}
return d->to;
}
qint64 Range::length() const
{
if (!isValid()) {
return -1;
}
// Last n bytes
if (d->from < 0) {
return -(d->from);
}
// From and to are set
if (d->to >= 0) {
return d->to - d->from + 1;
}
// From to to end
if (d->dataSize >= 0) {
return d->dataSize - d->from;
}
return -1;
}
qint64 Range::dataSize() const
{
return d->dataSize;
}
bool Range::isValid() const
{
// Valid cases:
// 1. "-500/1000" => from: -500, to: -1; dataSize: 1000
// 2. "10-/1000" => from: 10, to: -1; dataSize: 1000
// 3. "10-600/1000" => from: 10, to: 600; dataSize: 1000
// 4. "-500/*" => from: -500, to: -1; dataSize: -1
// 5. "10-/*" => from: 10, to: -1; dataSize: -1
// 6. "10-600/*" => from: 10, to: 600; dataSize: -1
// DataSize is set
if (d->dataSize >= 0) {
if (d->from < 0) { // Last n bytes
// Check if from is in range of dataSize
if (d->dataSize + d->from >= 0) {
return true;
}
} else {
if (d->to <= -1) { // To isn't set, range is up to the end
// Check if from is in range of dataSize
if (d->from < d->dataSize) {
return true;
}
} else { // from, to and dataSize are set
if (d->from <= d->to && d->to < d->dataSize) {
return true;
}
}
}
} else { // dataSize is not set
if (d->from < 0) { // Last n bytes
return true;
} else {
if (d->to <= -1) { // To isn't set, range is up to the end
return true;
} else { // from and to are set
if (d->from <= d->to) {
return true;
}
}
}
}
return false;
}
QString Range::contentRange() const
{
QString fromStr, toStr, sizeStr = "*";
if (d->dataSize >= 0) {
if (isValid()) {
fromStr = QString::number(from());
toStr = QString::number(to());
sizeStr = QString::number(dataSize());
} else {
sizeStr = QString::number(dataSize());
}
} else {
if (isValid()) {
fromStr = QString::number(from());
toStr = QString::number(to());
} else {
return "";
}
}
if (fromStr.isEmpty() || toStr.isEmpty()) {
return QString("*/%1").arg(sizeStr);
}
return QString("%1-%2/%3").arg(fromStr, toStr, sizeStr);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Aleksei Ermakov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -20,17 +20,29 @@
* IN THE SOFTWARE.
*/
#ifndef QHTTPENGINE_QHTTPENGINE_H
#define QHTTPENGINE_QHTTPENGINE_H
#ifndef QHTTPENGINE_RANGE_P_H
#define QHTTPENGINE_RANGE_P_H
#include <QtCore/qglobal.h>
#include <qhttpengine/range.h>
#define QHTTPENGINE_VERSION "@PROJECT_VERSION@"
namespace QHttpEngine
{
#if defined(QHTTPENGINE_LIBRARY)
# define QHTTPENGINE_EXPORT Q_DECL_EXPORT
#else
# define QHTTPENGINE_EXPORT Q_DECL_IMPORT
#endif
class RangePrivate
{
public:
#endif // QHTTPENGINE_QHTTPENGINE_H
explicit RangePrivate(Range *range);
qint64 from;
qint64 to;
qint64 dataSize;
private:
Range *const q;
};
}
#endif // QHTTPENGINE_RANGE_P_H

113
src/src/server.cpp Normal file
View File

@@ -0,0 +1,113 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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.
*/
#if !defined(QT_NO_SSL)
# include <QSslSocket>
#endif
#include <qhttpengine/handler.h>
#include <qhttpengine/socket.h>
#include "server_p.h"
using namespace QHttpEngine;
ServerPrivate::ServerPrivate(Server *httpServer)
: QObject(httpServer),
q(httpServer),
handler(0)
{
}
void ServerPrivate::process(QTcpSocket *socket)
{
Socket *httpSocket = new Socket(socket, this);
// Wait until the socket finishes reading the HTTP headers before routing
connect(httpSocket, &Socket::headersParsed, [this, httpSocket]() {
if (handler) {
handler->route(httpSocket, QString(httpSocket->path().mid(1)));
} else {
httpSocket->writeError(Socket::InternalServerError);
}
});
}
Server::Server(QObject *parent)
: QTcpServer(parent),
d(new ServerPrivate(this))
{
}
Server::Server(Handler *handler, QObject *parent)
: QTcpServer(parent),
d(new ServerPrivate(this))
{
setHandler(handler);
}
void Server::setHandler(Handler *handler)
{
d->handler = handler;
}
#if !defined(QT_NO_SSL)
void Server::setSslConfiguration(const QSslConfiguration &configuration)
{
d->configuration = configuration;
}
#endif
void Server::incomingConnection(qintptr socketDescriptor)
{
#if !defined(QT_NO_SSL)
if (!d->configuration.isNull()) {
// Initialize the socket with the SSL configuration
QSslSocket *socket = new QSslSocket(this);
// Wait until encryption is complete before processing the socket
connect(socket, &QSslSocket::encrypted, [this, socket]() {
d->process(socket);
});
// If an error occurs, delete the socket
connect(socket, static_cast<void(QAbstractSocket::*)(QAbstractSocket::SocketError)>(&QAbstractSocket::error),
socket, &QSslSocket::deleteLater);
socket->setSocketDescriptor(socketDescriptor);
socket->setSslConfiguration(d->configuration);
socket->startServerEncryption();
} else {
#endif
QTcpSocket *socket = new QTcpSocket(this);
socket->setSocketDescriptor(socketDescriptor);
// Process the socket immediately
d->process(socket);
#if !defined(QT_NO_SSL)
}
#endif
}

63
src/src/server_p.h Normal file
View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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 QHTTPENGINE_SERVER_P_H
#define QHTTPENGINE_SERVER_P_H
#include <QObject>
#include <QTcpSocket>
#if !defined(QT_NO_SSL)
# include <QSslConfiguration>
#endif
#include <qhttpengine/server.h>
namespace QHttpEngine
{
class Handler;
class ServerPrivate : public QObject
{
Q_OBJECT
public:
explicit ServerPrivate(Server *httpServer);
void process(QTcpSocket *socket);
Handler *handler;
#if !defined(QT_NO_SSL)
QSslConfiguration configuration;
#endif
private:
Server *const q;
};
}
#endif // QHTTPENGINE_SERVER_P_H

397
src/src/socket.cpp Normal file
View File

@@ -0,0 +1,397 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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.
*/
#include <cstring>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QTcpSocket>
#include <qhttpengine/parser.h>
#include "socket_p.h"
using namespace QHttpEngine;
// Predefined error response requires a simple HTML template to be returned to
// the client describing the error condition
const QString ErrorTemplate =
"<!DOCTYPE html>"
"<html>"
"<head>"
"<meta charset=\"utf-8\">"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<title>%1 %2</title>"
"</head>"
"<body>"
"<h1>%1 %2</h1>"
"<p>"
"An error has occurred while trying to display the requested resource. "
"Please contact the website owner if this error persists."
"</p>"
"<hr>"
"<p><em>QHttpEngine %3</em></p>"
"</body>"
"</html>";
SocketPrivate::SocketPrivate(Socket *httpSocket, QTcpSocket *tcpSocket)
: QObject(httpSocket),
q(httpSocket),
socket(tcpSocket),
readState(ReadHeaders),
requestDataRead(0),
requestDataTotal(-1),
writeState(WriteNone),
responseStatusCode(200),
responseStatusReason(statusReason(200))
{
socket->setParent(this);
connect(socket, &QTcpSocket::readyRead, this, &SocketPrivate::onReadyRead);
connect(socket, &QTcpSocket::bytesWritten, this, &SocketPrivate::onBytesWritten);
connect(socket, &QTcpSocket::readChannelFinished, this, &SocketPrivate::onReadChannelFinished);
connect(socket, &QTcpSocket::disconnected, q, &Socket::disconnected);
// Process anything already received by the socket
onReadyRead();
}
QByteArray SocketPrivate::statusReason(int statusCode) const
{
switch (statusCode) {
case Socket::OK: return "OK";
case Socket::Created: return "CREATED";
case Socket::Accepted: return "ACCEPTED";
case Socket::PartialContent: return "PARTIAL CONTENT";
case Socket::MovedPermanently: return "MOVED PERMANENTLY";
case Socket::Found: return "FOUND";
case Socket::BadRequest: return "BAD REQUEST";
case Socket::Unauthorized: return "UNAUTHORIZED";
case Socket::Forbidden: return "FORBIDDEN";
case Socket::NotFound: return "NOT FOUND";
case Socket::MethodNotAllowed: return "METHOD NOT ALLOWED";
case Socket::Conflict: return "CONFLICT";
case Socket::BadGateway: return "BAD GATEWAY";
case Socket::ServiceUnavailable: return "SERVICE UNAVAILABLE";
case Socket::InternalServerError: return "INTERNAL SERVER ERROR";
case Socket::HttpVersionNotSupported: return "HTTP VERSION NOT SUPPORTED";
default: return "UNKNOWN ERROR";
}
}
void SocketPrivate::onReadyRead()
{
// Append all of the new data to the read buffer
readBuffer.append(socket->readAll());
// If reading headers, return if they could not be read (yet)
if (readState == ReadHeaders && !readHeaders()) {
return;
}
// Read data if in that state, otherwise discard
switch (readState) {
case ReadData:
readData();
break;
case ReadFinished:
readBuffer.clear();
break;
}
}
void SocketPrivate::onBytesWritten(qint64 bytes)
{
// Check to see if all of the response header was written
if (writeState == WriteHeaders) {
if (responseHeaderRemaining - bytes > 0) {
responseHeaderRemaining -= bytes;
} else {
writeState = WriteData;
bytes -= responseHeaderRemaining;
}
}
// Only emit bytesWritten() for data after the headers
if (writeState == WriteData) {
Q_EMIT q->bytesWritten(bytes);
}
}
void SocketPrivate::onReadChannelFinished()
{
if (requestDataTotal == -1) {
emit q->readChannelFinished();
}
}
bool SocketPrivate::readHeaders()
{
// Check for the double CRLF that signals the end of the headers and
// if it is not found, wait until the next time readyRead is emitted
int index = readBuffer.indexOf("\r\n\r\n");
if (index == -1) {
return false;
}
// Attempt to parse the headers and if a problem is encountered, abort
// the connection (so that no more data is read or written) and return
if (!Parser::parseRequestHeaders(readBuffer.left(index), requestMethod, requestRawPath, requestHeaders) ||
!Parser::parsePath(requestRawPath, requestPath, requestQueryString)) {
q->writeError(Socket::BadRequest);
return false;
}
// Remove the headers from the buffer
readBuffer.remove(0, index + 4);
readState = ReadData;
// If the content-length header is present, use it to determine
// how much data to expect from the socket - not all requests
// use this header - WebSocket requests, for example, do not
if (requestHeaders.contains("Content-Length")) {
requestDataTotal = requestHeaders.value("Content-Length").toLongLong();
}
// Indicate that the headers have been parsed
Q_EMIT q->headersParsed();
return true;
}
void SocketPrivate::readData()
{
// Emit the readyRead() signal if any data is available in the buffer
if (readBuffer.size()) {
Q_EMIT q->readyRead();
}
// Check to see if the specified amount of data has been read from the
// socket, if so, emit the readChannelFinished() signal
if (requestDataTotal != -1 &&
requestDataRead + readBuffer.size() >= requestDataTotal) {
readState = ReadFinished;
Q_EMIT q->readChannelFinished();
}
}
Socket::Socket(QTcpSocket *socket, QObject *parent)
: QIODevice(parent),
d(new SocketPrivate(this, socket))
{
// The device is initially open for both reading and writing
setOpenMode(QIODevice::ReadWrite);
}
qint64 Socket::bytesAvailable() const
{
if (d->readState > SocketPrivate::ReadHeaders) {
return d->readBuffer.size() + QIODevice::bytesAvailable();
} else {
return 0;
}
}
bool Socket::isSequential() const
{
return true;
}
void Socket::close()
{
// Invoke the parent method
QIODevice::close();
d->readState = SocketPrivate::ReadFinished;
d->writeState = SocketPrivate::WriteFinished;
connect(d->socket, &QTcpSocket::disconnected, this, &Socket::deleteLater);
d->socket->close();
}
QHostAddress Socket::peerAddress() const
{
return d->socket->peerAddress();
}
bool Socket::isHeadersParsed() const
{
return d->readState > SocketPrivate::ReadHeaders;
}
Socket::Method Socket::method() const
{
return d->requestMethod;
}
QByteArray Socket::rawPath() const
{
return d->requestRawPath;
}
QString Socket::path() const
{
return d->requestPath;
}
Socket::QueryStringMap Socket::queryString() const
{
return d->requestQueryString;
}
Socket::HeaderMap Socket::headers() const
{
return d->requestHeaders;
}
qint64 Socket::contentLength() const
{
return d->requestDataTotal;
}
bool Socket::readJson(QJsonDocument &document)
{
QJsonParseError error;
document = QJsonDocument::fromJson(readAll(), &error);
if (error.error != QJsonParseError::NoError) {
writeError(Socket::BadRequest);
return false;
}
return true;
}
void Socket::setStatusCode(int statusCode, const QByteArray &statusReason)
{
d->responseStatusCode = statusCode;
d->responseStatusReason = statusReason.isNull() ? d->statusReason(statusCode) : statusReason;
}
void Socket::setHeader(const QByteArray &name, const QByteArray &value, bool replace)
{
if (replace || d->responseHeaders.count(name)) {
d->responseHeaders.replace(name, value);
} else {
d->responseHeaders.replace(name, d->responseHeaders.value(name) + ", " + value);
}
}
void Socket::setHeaders(const HeaderMap &headers)
{
d->responseHeaders = headers;
}
void Socket::writeHeaders()
{
// Use a QByteArray for building the header so that we can later determine
// exactly how many bytes were written
QByteArray header;
// Append the status line
header.append("HTTP/1.0 ");
header.append(QByteArray::number(d->responseStatusCode) + " " + d->responseStatusReason);
header.append("\r\n");
// Append each of the headers followed by a CRLF
for (auto i = d->responseHeaders.constBegin(); i != d->responseHeaders.constEnd(); ++i) {
header.append(i.key());
header.append(": ");
header.append(d->responseHeaders.values(i.key()).join(", "));
header.append("\r\n");
}
// Append an extra CRLF
header.append("\r\n");
d->writeState = SocketPrivate::WriteHeaders;
d->responseHeaderRemaining = header.length();
// Write the header
d->socket->write(header);
}
void Socket::writeRedirect(const QByteArray &path, bool permanent)
{
setStatusCode(permanent ? MovedPermanently : Found);
setHeader("Location", path);
writeHeaders();
close();
}
void Socket::writeError(int statusCode, const QByteArray &statusReason)
{
setStatusCode(statusCode, statusReason);
// Build the template that will be sent to the client
QByteArray data = ErrorTemplate
.arg(d->responseStatusCode)
.arg(d->responseStatusReason.constData())
.arg(QHTTPENGINE_VERSION)
.toUtf8();
setHeader("Content-Length", QByteArray::number(data.length()));
setHeader("Content-Type", "text/html");
writeHeaders();
write(data);
close();
}
void Socket::writeJson(const QJsonDocument &document, int statusCode)
{
QByteArray data = document.toJson();
setStatusCode(statusCode);
setHeader("Content-Length", QByteArray::number(data.length()));
setHeader("Content-Type", "application/json");
write(data);
close();
}
qint64 Socket::readData(char *data, qint64 maxlen)
{
// Ensure the connection is in the correct state for reading data
if (d->readState == SocketPrivate::ReadHeaders) {
return 0;
}
// Ensure that no more than the requested amount or the size of the buffer is read
qint64 size = qMin(static_cast<qint64>(d->readBuffer.size()), maxlen);
memcpy(data, d->readBuffer.constData(), size);
// Remove the amount that was read from the buffer
d->readBuffer.remove(0, size);
d->requestDataRead += size;
return size;
}
qint64 Socket::writeData(const char *data, qint64 len)
{
// If the response headers have not yet been written, they must
// immediately be written before the data can be
if (d->writeState == SocketPrivate::WriteNone) {
writeHeaders();
}
return d->socket->write(data, len);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -20,22 +20,23 @@
* IN THE SOFTWARE.
*/
#ifndef QHTTPENGINE_QHTTPSOCKETPRIVATE_H
#define QHTTPENGINE_QHTTPSOCKETPRIVATE_H
#ifndef QHTTPENGINE_SOCKET_P_H
#define QHTTPENGINE_SOCKET_P_H
#include <QObject>
#include <QTcpSocket>
#include <qhttpengine/socket.h>
#include "QHttpEngine/qhttpparser.h"
#include "QHttpEngine/qhttpsocket.h"
class QTcpSocket;
class QHttpSocketPrivate : public QObject
namespace QHttpEngine
{
class SocketPrivate : public QObject
{
Q_OBJECT
public:
QHttpSocketPrivate(QHttpSocket *httpSocket, QTcpSocket *tcpSocket);
SocketPrivate(Socket *httpSocket, QTcpSocket *tcpSocket);
QByteArray statusReason(int statusCode) const;
@@ -48,9 +49,11 @@ public:
ReadFinished
} readState;
QByteArray requestMethod;
QByteArray requestPath;
QHttpHeaderMap requestHeaders;
Socket::Method requestMethod;
QByteArray requestRawPath;
QString requestPath;
Socket::QueryStringMap requestQueryString;
Socket::HeaderMap requestHeaders;
qint64 requestDataRead;
qint64 requestDataTotal;
@@ -63,20 +66,23 @@ public:
int responseStatusCode;
QByteArray responseStatusReason;
QHttpHeaderMap responseHeaders;
Socket::HeaderMap responseHeaders;
qint64 responseHeaderRemaining;
private Q_SLOTS:
void onReadyRead();
void onBytesWritten(qint64 bytes);
void onReadChannelFinished();
private:
bool readHeaders();
void readData();
QHttpSocket *const q;
Socket*const q;
};
#endif // QHTTPENGINE_QHTTPSOCKETPRIVATE_H
}
#endif // QHTTPENGINE_SOCKET_P_H

View File

@@ -1,23 +1,32 @@
find_package(Qt5Test 5.1 REQUIRED)
add_subdirectory(common)
set(TESTS
TestQFilesystemHandler
TestQHttpHandler
TestQHttpParser
TestQHttpServer
TestQHttpSocket
TestQIByteArray
TestBasicAuthMiddleware
TestFilesystemHandler
TestHandler
TestIByteArray
TestLocalAuthMiddleware
TestLocalFile
TestMiddleware
TestParser
TestProxyHandler
TestQIODeviceCopier
TestQLocalFile
TestQObjectHandler
TestRange
TestServer
TestSocket
)
qt5_add_resources(QRC resource.qrc)
foreach(TEST ${TESTS})
add_executable(${TEST} ${TEST}.cpp)
qt5_use_modules(${TEST} Test)
target_link_libraries(${TEST} QHttpEngine common)
add_executable(${TEST} ${TEST}.cpp ${QRC})
set_target_properties(${TEST} PROPERTIES
CXX_STANDARD 11
CXX_STANDARD_REQUIRED ON
)
target_include_directories(${TEST} PUBLIC "${CMAKE_CURRENT_BINARY_DIR}")
target_link_libraries(${TEST} Qt5::Test qhttpengine common)
add_test(NAME ${TEST}
COMMAND ${TEST}
)
@@ -26,8 +35,8 @@ endforeach()
# On Windows, the library's DLL must exist in the same directory as the test
# executables which link against it - create a custom command to copy it
if(WIN32 AND NOT BUILD_STATIC)
add_custom_target(QHttpEngine-copy ALL
"${CMAKE_COMMAND}" -E copy_if_different "$<TARGET_FILE:QHttpEngine>" "${CMAKE_CURRENT_BINARY_DIR}"
DEPENDS QHttpEngine
add_custom_target(qhttpengine-copy ALL
"${CMAKE_COMMAND}" -E copy_if_different \"$<TARGET_FILE:qhttpengine>\" \"${CMAKE_CURRENT_BINARY_DIR}\"
DEPENDS qhttpengine
)
endif()

View File

@@ -0,0 +1,119 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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.
*/
#include <QTest>
#include <qhttpengine/basicauthmiddleware.h>
#include <qhttpengine/handler.h>
#include <qhttpengine/socket.h>
#include "common/qsimplehttpclient.h"
#include "common/qsocketpair.h"
const QString Username = "username";
const QString Password = "password";
class TestBasicAuthMiddleware : public QObject
{
Q_OBJECT
public:
TestBasicAuthMiddleware() : auth("Test") {}
private Q_SLOTS:
void initTestCase();
void testProcess_data();
void testProcess();
private:
QHttpEngine::BasicAuthMiddleware auth;
};
void TestBasicAuthMiddleware::initTestCase()
{
auth.add(Username, Password);
}
void TestBasicAuthMiddleware::testProcess_data()
{
QTest::addColumn<bool>("header");
QTest::addColumn<QString>("username");
QTest::addColumn<QString>("password");
QTest::addColumn<int>("status");
QTest::newRow("no header")
<< false
<< QString()
<< QString()
<< static_cast<int>(QHttpEngine::Socket::Unauthorized);
QTest::newRow("invalid credentials")
<< true
<< Username
<< QString()
<< static_cast<int>(QHttpEngine::Socket::Unauthorized);
QTest::newRow("valid credentials")
<< true
<< Username
<< Password
<< static_cast<int>(QHttpEngine::Socket::NotFound);
}
void TestBasicAuthMiddleware::testProcess()
{
QFETCH(bool, header);
QFETCH(QString, username);
QFETCH(QString, password);
QFETCH(int, status);
QSocketPair pair;
QTRY_VERIFY(pair.isConnected());
QSimpleHttpClient client(pair.client());
QHttpEngine::Socket *socket = new QHttpEngine::Socket(pair.server(), &pair);
QHttpEngine::Socket::HeaderMap headers;
if (header) {
headers.insert(
"Authorization",
"Basic " + QString("%1:%2").arg(username).arg(password).toUtf8().toBase64()
);
}
client.sendHeaders("GET", "/", headers);
QTRY_VERIFY(socket->isHeadersParsed());
QHttpEngine::Handler handler;
handler.addMiddleware(&auth);
handler.route(socket, "/");
QTRY_COMPARE(client.statusCode(), status);
}
QTEST_MAIN(TestBasicAuthMiddleware)
#include "TestBasicAuthMiddleware.moc"

View File

@@ -0,0 +1,212 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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.
*/
#include <QDir>
#include <QFile>
#include <QObject>
#include <QSignalSpy>
#include <QTemporaryDir>
#include <QTest>
#include <qhttpengine/socket.h>
#include <qhttpengine/filesystemhandler.h>
#include "common/qsimplehttpclient.h"
#include "common/qsocketpair.h"
const QByteArray Data = "test";
class TestFilesystemHandler : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void testRangeRequests_data();
void testRangeRequests();
void testRequests_data();
void testRequests();
private:
bool createFile(const QString &path);
bool createDirectory(const QString &path);
QTemporaryDir dir;
};
void TestFilesystemHandler::initTestCase()
{
QVERIFY(createFile("outside"));
QVERIFY(createDirectory("root"));
QVERIFY(createFile("root/inside"));
}
void TestFilesystemHandler::testRequests_data()
{
QTest::addColumn<QString>("path");
QTest::addColumn<int>("statusCode");
QTest::addColumn<QByteArray>("data");
QTest::newRow("nonexistent resource")
<< "nonexistent"
<< static_cast<int>(QHttpEngine::Socket::NotFound)
<< QByteArray();
QTest::newRow("outside document root")
<< "../outside"
<< static_cast<int>(QHttpEngine::Socket::NotFound)
<< QByteArray();
QTest::newRow("inside document root")
<< "inside"
<< static_cast<int>(QHttpEngine::Socket::OK)
<< Data;
QTest::newRow("directory listing")
<< ""
<< static_cast<int>(QHttpEngine::Socket::OK)
<< QByteArray();
}
void TestFilesystemHandler::testRequests()
{
QFETCH(QString, path);
QFETCH(int, statusCode);
QFETCH(QByteArray, data);
QHttpEngine::FilesystemHandler handler(QDir(dir.path()).absoluteFilePath("root"));
QSocketPair pair;
QTRY_VERIFY(pair.isConnected());
QSimpleHttpClient client(pair.client());
QHttpEngine::Socket *socket = new QHttpEngine::Socket(pair.server(), &pair);
handler.route(socket, path);
QTRY_COMPARE(client.statusCode(), statusCode);
if (!data.isNull()) {
QTRY_COMPARE(client.data(), data);
}
}
void TestFilesystemHandler::testRangeRequests_data()
{
QTest::addColumn<QString>("path");
QTest::addColumn<QString>("range");
QTest::addColumn<int>("statusCode");
QTest::addColumn<QString>("contentRange");
QTest::addColumn<QByteArray>("data");
QTest::newRow("full file")
<< "inside" << ""
<< static_cast<int>(QHttpEngine::Socket::OK)
<< ""
<< Data;
QTest::newRow("range 0-2")
<< "inside" << "0-2"
<< static_cast<int>(QHttpEngine::Socket::PartialContent)
<< "bytes 0-2/4"
<< Data.mid(0, 3);
QTest::newRow("range 1-2")
<< "inside" << "1-2"
<< static_cast<int>(QHttpEngine::Socket::PartialContent)
<< "bytes 1-2/4"
<< Data.mid(1, 2);
QTest::newRow("skip first 1 byte")
<< "inside" << "1-"
<< static_cast<int>(QHttpEngine::Socket::PartialContent)
<< "bytes 1-3/4"
<< Data.mid(1);
QTest::newRow("last 2 bytes")
<< "inside" << "-2"
<< static_cast<int>(QHttpEngine::Socket::PartialContent)
<< "bytes 2-3/4"
<< Data.mid(2);
QTest::newRow("bad range request")
<< "inside" << "abcd"
<< static_cast<int>(QHttpEngine::Socket::OK)
<< ""
<< Data;
}
void TestFilesystemHandler::testRangeRequests()
{
QFETCH(QString, path);
QFETCH(QString, range);
QFETCH(int, statusCode);
QFETCH(QString, contentRange);
QFETCH(QByteArray, data);
QHttpEngine::FilesystemHandler handler(QDir(dir.path()).absoluteFilePath("root"));
QSocketPair pair;
QTRY_VERIFY(pair.isConnected());
QSimpleHttpClient client(pair.client());
QHttpEngine::Socket *socket = new QHttpEngine::Socket(pair.server(), &pair);
if (!range.isEmpty()) {
QHttpEngine::Socket::HeaderMap inHeaders;
inHeaders.insert("Range", QByteArray("bytes=") + range.toUtf8());
client.sendHeaders("GET", path.toUtf8(), inHeaders);
QTRY_VERIFY(socket->isHeadersParsed());
}
handler.route(socket, path);
QTRY_COMPARE(client.statusCode(), statusCode);
if (!data.isNull()) {
QTRY_COMPARE(client.data(), data);
QCOMPARE(client.headers().value("Content-Length").toInt(), data.length());
QCOMPARE(client.headers().value("Content-Range"), contentRange.toLatin1());
}
}
bool TestFilesystemHandler::createFile(const QString &path)
{
QFile file(QDir(dir.path()).absoluteFilePath(path));
if (!file.open(QIODevice::WriteOnly)) {
return false;
}
return file.write(Data) == Data.length();
}
bool TestFilesystemHandler::createDirectory(const QString &path)
{
return QDir(dir.path()).mkpath(path);
}
QTEST_MAIN(TestFilesystemHandler)
#include "TestFilesystemHandler.moc"

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -23,19 +23,19 @@
#include <QRegExp>
#include <QTest>
#include <QHttpEngine/QHttpSocket>
#include <QHttpEngine/QHttpHandler>
#include <qhttpengine/socket.h>
#include <qhttpengine/handler.h>
#include "common/qsimplehttpclient.h"
#include "common/qsocketpair.h"
class DummyHandler : public QHttpHandler
class DummyHandler : public QHttpEngine::Handler
{
Q_OBJECT
public:
virtual void process(QHttpSocket *socket, const QString &path) {
virtual void process(QHttpEngine::Socket *socket, const QString &path) {
mPathRemainder = path;
socket->writeHeaders();
socket->close();
@@ -44,7 +44,7 @@ public:
QString mPathRemainder;
};
class TestQHttpHandler : public QObject
class TestHandler : public QObject
{
Q_OBJECT
@@ -57,7 +57,7 @@ private Q_SLOTS:
void testSubHandler();
};
void TestQHttpHandler::testRedirect_data()
void TestHandler::testRedirect_data()
{
QTest::addColumn<QRegExp>("pattern");
QTest::addColumn<QString>("destination");
@@ -69,24 +69,24 @@ void TestQHttpHandler::testRedirect_data()
<< QRegExp("\\w+")
<< QString("/two")
<< QByteArray("one")
<< static_cast<int>(QHttpSocket::Found)
<< static_cast<int>(QHttpEngine::Socket::Found)
<< QByteArray("/two");
QTest::newRow("no match")
<< QRegExp("\\d+")
<< QString("")
<< QByteArray("test")
<< static_cast<int>(QHttpSocket::NotFound);
<< static_cast<int>(QHttpEngine::Socket::NotFound);
QTest::newRow("captured texts")
<< QRegExp("(\\d+)")
<< QString("/path/%1")
<< QByteArray("123")
<< static_cast<int>(QHttpSocket::Found)
<< static_cast<int>(QHttpEngine::Socket::Found)
<< QByteArray("/path/123");
}
void TestQHttpHandler::testRedirect()
void TestHandler::testRedirect()
{
QFETCH(QRegExp, pattern);
QFETCH(QString, destination);
@@ -97,24 +97,24 @@ void TestQHttpHandler::testRedirect()
QTRY_VERIFY(pair.isConnected());
QSimpleHttpClient client(pair.client());
QHttpSocket socket(pair.server(), &pair);
QHttpEngine::Socket *socket = new QHttpEngine::Socket(pair.server(), &pair);
client.sendHeaders("GET", path, QHttpHeaderMap());
QTRY_VERIFY(socket.isHeadersParsed());
client.sendHeaders("GET", path);
QTRY_VERIFY(socket->isHeadersParsed());
QHttpHandler handler;
QHttpEngine::Handler handler;
handler.addRedirect(pattern, destination);
handler.route(&socket, socket.path());
handler.route(socket, socket->path());
QTRY_COMPARE(client.statusCode(), statusCode);
if(statusCode == QHttpSocket::Found) {
if (statusCode == QHttpEngine::Socket::Found) {
QFETCH(QByteArray, location);
QCOMPARE(client.headers().value("Location"), location);
}
}
void TestQHttpHandler::testSubHandler_data()
void TestHandler::testSubHandler_data()
{
QTest::addColumn<QRegExp>("pattern");
QTest::addColumn<QByteArray>("path");
@@ -125,22 +125,22 @@ void TestQHttpHandler::testSubHandler_data()
<< QRegExp("\\w+")
<< QByteArray("test")
<< QString("")
<< static_cast<int>(QHttpSocket::OK);
<< static_cast<int>(QHttpEngine::Socket::OK);
QTest::newRow("no match")
<< QRegExp("\\d+")
<< QByteArray("test")
<< QString("")
<< static_cast<int>(QHttpSocket::NotFound);
<< static_cast<int>(QHttpEngine::Socket::NotFound);
QTest::newRow("path")
<< QRegExp("one/")
<< QByteArray("one/two")
<< QString("two")
<< static_cast<int>(QHttpSocket::OK);
<< static_cast<int>(QHttpEngine::Socket::OK);
}
void TestQHttpHandler::testSubHandler()
void TestHandler::testSubHandler()
{
QFETCH(QRegExp, pattern);
QFETCH(QByteArray, path);
@@ -151,20 +151,20 @@ void TestQHttpHandler::testSubHandler()
QTRY_VERIFY(pair.isConnected());
QSimpleHttpClient client(pair.client());
QHttpSocket socket(pair.server(), &pair);
QHttpEngine::Socket *socket = new QHttpEngine::Socket(pair.server(), &pair);
client.sendHeaders("GET", path, QHttpHeaderMap());
QTRY_VERIFY(socket.isHeadersParsed());
client.sendHeaders("GET", path);
QTRY_VERIFY(socket->isHeadersParsed());
DummyHandler subHandler;
QHttpHandler handler;
QHttpEngine::Handler handler;
handler.addSubHandler(pattern, &subHandler);
handler.route(&socket, socket.path());
handler.route(socket, socket->path());
QTRY_COMPARE(client.statusCode(), statusCode);
QCOMPARE(subHandler.mPathRemainder, pathRemainder);
}
QTEST_MAIN(TestQHttpHandler)
#include "TestQHttpHandler.moc"
QTEST_MAIN(TestHandler)
#include "TestHandler.moc"

69
tests/TestIByteArray.cpp Normal file
View File

@@ -0,0 +1,69 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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.
*/
#include <QObject>
#include <QTest>
#include <qhttpengine/ibytearray.h>
const char *Value1 = "test";
const char *Value2 = "TEST";
// Helpful macros to cut down on the amount of duplicated code
#define TEST_OPERATOR(tn,t,on,o,v) void test##tn##on() \
{ \
QCOMPARE(QHttpEngine::IByteArray(Value1) o static_cast<t>(Value2), v); \
QCOMPARE(static_cast<t>(Value1) o QHttpEngine::IByteArray(Value1), v); \
}
#define TEST_TYPE(tn,t) \
TEST_OPERATOR(tn, t, Equals, ==, true) \
TEST_OPERATOR(tn, t, NotEquals, !=, false) \
TEST_OPERATOR(tn, t, Less, <, false) \
TEST_OPERATOR(tn, t, Greater, >, false) \
TEST_OPERATOR(tn, t, LessEqual, <=, true) \
TEST_OPERATOR(tn, t, GreaterEqual, >=, true)
class TestIByteArray : public QObject
{
Q_OBJECT
private Q_SLOTS:
TEST_TYPE(ConstChar, const char *)
TEST_TYPE(QByteArray, QByteArray)
TEST_TYPE(IByteArray, QHttpEngine::IByteArray)
TEST_TYPE(QString, QString)
void testContains();
};
void TestIByteArray::testContains()
{
QHttpEngine::IByteArray v(Value1);
QVERIFY(v.contains('t'));
QVERIFY(v.contains(Value2));
QVERIFY(v.contains(QByteArray(Value2)));
}
QTEST_MAIN(TestIByteArray)
#include "TestIByteArray.moc"

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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.
*/
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QScopedPointer>
#include <QTest>
#include <QVariantMap>
#include <qhttpengine/socket.h>
#include <qhttpengine/localauthmiddleware.h>
#include "common/qsimplehttpclient.h"
#include "common/qsocketpair.h"
const QByteArray HeaderName = "X-Test";
const QByteArray CustomName = "Name";
const QByteArray CustomData = "Data";
class TestLocalAuthMiddleware : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testAuth();
void testRemoval();
};
void TestLocalAuthMiddleware::testAuth()
{
QSocketPair pair;
QTRY_VERIFY(pair.isConnected());
QSimpleHttpClient client(pair.client());
QHttpEngine::Socket socket(pair.server(), &pair);
QHttpEngine::LocalAuthMiddleware localAuth;
localAuth.setData(QVariantMap{
{CustomName, CustomData}
});
localAuth.setHeaderName(HeaderName);
QVERIFY(localAuth.exists());
QFile file(localAuth.filename());
QVERIFY(file.open(QIODevice::ReadOnly));
QVariantMap data = QJsonDocument::fromJson(file.readAll()).object().toVariantMap();
QVERIFY(data.contains("token"));
QCOMPARE(data.value(CustomName).toByteArray(), CustomData);
client.sendHeaders("GET", "/", QHttpEngine::Socket::HeaderMap{
{HeaderName, data.value("token").toByteArray()}
});
QTRY_VERIFY(socket.isHeadersParsed());
QVERIFY(localAuth.process(&socket));
}
void TestLocalAuthMiddleware::testRemoval()
{
QScopedPointer<QHttpEngine::LocalAuthMiddleware> localAuth(
new QHttpEngine::LocalAuthMiddleware);
QString filename = localAuth->filename();
QVERIFY(QFile::exists(filename));
delete localAuth.take();
QVERIFY(!QFile::exists(filename));
}
QTEST_MAIN(TestLocalAuthMiddleware)
#include "TestLocalAuthMiddleware.moc"

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -25,11 +25,11 @@
#include <QObject>
#include <QTest>
#include <QHttpEngine/QLocalFile>
#include <qhttpengine/localfile.h>
const QString ApplicationName = "QHttpEngine";
class TestQLocalFile : public QObject
class TestLocalFile : public QObject
{
Q_OBJECT
@@ -39,17 +39,17 @@ private Q_SLOTS:
void testOpen();
};
void TestQLocalFile::initTestCase()
void TestLocalFile::initTestCase()
{
QCoreApplication::setApplicationName(ApplicationName);
}
void TestQLocalFile::testOpen()
void TestLocalFile::testOpen()
{
QLocalFile file;
QHttpEngine::LocalFile file;
QVERIFY(file.open());
QVERIFY(file.remove());
}
QTEST_MAIN(TestQLocalFile)
#include "TestQLocalFile.moc"
QTEST_MAIN(TestLocalFile)
#include "TestLocalFile.moc"

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -20,62 +20,55 @@
* IN THE SOFTWARE.
*/
#include <QSignalSpy>
#include <QTcpSocket>
#include <QTest>
#include <QHttpEngine/QHttpServer>
#include <QHttpEngine/QHttpHandler>
#include <qhttpengine/handler.h>
#include <qhttpengine/middleware.h>
#include <qhttpengine/socket.h>
#include "common/qsimplehttpclient.h"
#include "common/qsocketpair.h"
class TestHandler : public QHttpHandler
class DummyMiddleware : public QHttpEngine::Middleware
{
Q_OBJECT
public:
TestHandler() : mSocket(0) {}
virtual void process(QHttpSocket *socket, const QString &path) {
mSocket = socket;
mPath = path;
virtual bool process(QHttpEngine::Socket *socket)
{
socket->writeError(QHttpEngine::Socket::Forbidden);
return false;
}
QHttpSocket *mSocket;
QString mPath;
};
class TestQHttpServer : public QObject
class TestMiddleware : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testServer();
void testProcess();
};
void TestQHttpServer::testServer()
void TestMiddleware::testProcess()
{
TestHandler handler;
QHttpServer server(&handler);
QSocketPair pair;
QTRY_VERIFY(pair.isConnected());
QVERIFY(server.listen(QHostAddress::LocalHost));
QSimpleHttpClient client(pair.client());
QHttpEngine::Socket *socket = new QHttpEngine::Socket(pair.server(), &pair);
QTcpSocket socket;
socket.connectToHost(server.serverAddress(), server.serverPort());
QTRY_COMPARE(socket.state(), QAbstractSocket::ConnectedState);
client.sendHeaders("GET", "/");
QTRY_VERIFY(socket->isHeadersParsed());
QSimpleHttpClient client(&socket);
client.sendHeaders("GET", "/test", QHttpHeaderMap());
DummyMiddleware middleware;
QHttpEngine::Handler handler;
handler.addMiddleware(&middleware);
handler.route(socket, "/");
QTRY_VERIFY(handler.mSocket != 0);
QCOMPARE(handler.mPath, QString("test"));
QSignalSpy destroyedSpy(handler.mSocket, SIGNAL(destroyed()));
handler.mSocket->close();
QTRY_COMPARE(destroyedSpy.count(), 1);
QTRY_COMPARE(client.statusCode(), static_cast<int>(QHttpEngine::Socket::Forbidden));
}
QTEST_MAIN(TestQHttpServer)
#include "TestQHttpServer.moc"
QTEST_MAIN(TestMiddleware)
#include "TestMiddleware.moc"

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -24,34 +24,40 @@
#include <QObject>
#include <QTest>
#include <QHttpEngine/QHttpParser>
#include <QHttpEngine/QIByteArray>
#include <qhttpengine/parser.h>
#include <qhttpengine/socket.h>
#include <qhttpengine/ibytearray.h>
typedef QList<QByteArray> QByteArrayList;
Q_DECLARE_METATYPE(QHttpHeaderMap)
Q_DECLARE_METATYPE(QHttpEngine::Socket::Method)
Q_DECLARE_METATYPE(QHttpEngine::Socket::QueryStringMap)
Q_DECLARE_METATYPE(QHttpEngine::Socket::HeaderMap)
const QIByteArray Key1 = "a";
const QHttpEngine::IByteArray Key1 = "a";
const QByteArray Value1 = "b";
const QByteArray Line1 = Key1 + ": " + Value1;
const QIByteArray Key2 = "c";
const QHttpEngine::IByteArray Key2 = "c";
const QByteArray Value2 = "d";
const QByteArray Line2 = Key2 + ": " + Value2;
class TestQHttpParser : public QObject
class TestParser : public QObject
{
Q_OBJECT
public:
TestQHttpParser();
TestParser();
private Q_SLOTS:
void testSplit_data();
void testSplit();
void testParsePath_data();
void testParsePath();
void testParseHeaderList_data();
void testParseHeaderList();
@@ -66,16 +72,16 @@ private Q_SLOTS:
private:
QHttpHeaderMap headers;
QHttpEngine::Socket::HeaderMap headers;
};
TestQHttpParser::TestQHttpParser()
TestParser::TestParser()
{
headers.insert(Key1, Value1);
headers.insert(Key2, Value2);
}
void TestQHttpParser::testSplit_data()
void TestParser::testSplit_data()
{
QTest::addColumn<QByteArray>("data");
QTest::addColumn<QByteArray>("delim");
@@ -113,7 +119,7 @@ void TestQHttpParser::testSplit_data()
<< (QByteArrayList() << "a" << "a,a");
}
void TestQHttpParser::testSplit()
void TestParser::testSplit()
{
QFETCH(QByteArray, data);
QFETCH(QByteArray, delim);
@@ -121,16 +127,48 @@ void TestQHttpParser::testSplit()
QFETCH(QByteArrayList, parts);
QByteArrayList outParts;
QHttpParser::split(data, delim, maxSplit, outParts);
QHttpEngine::Parser::split(data, delim, maxSplit, outParts);
QCOMPARE(outParts, parts);
}
void TestQHttpParser::testParseHeaderList_data()
void TestParser::testParsePath_data()
{
QTest::addColumn<QByteArray>("rawPath");
QTest::addColumn<QString>("path");
QTest::addColumn<QHttpEngine::Socket::QueryStringMap>("map");
QTest::newRow("no query string")
<< QByteArray("/path")
<< QString("/path")
<< QHttpEngine::Socket::QueryStringMap();
QTest::newRow("single parameter")
<< QByteArray("/path?a=b")
<< QString("/path")
<< QHttpEngine::Socket::QueryStringMap{{"a", "b"}};
}
void TestParser::testParsePath()
{
QFETCH(QByteArray, rawPath);
QFETCH(QString, path);
QFETCH(QHttpEngine::Socket::QueryStringMap, map);
QString outPath;
QHttpEngine::Socket::QueryStringMap outMap;
QVERIFY(QHttpEngine::Parser::parsePath(rawPath, outPath, outMap));
QCOMPARE(path, outPath);
QCOMPARE(map, outMap);
}
void TestParser::testParseHeaderList_data()
{
QTest::addColumn<bool>("success");
QTest::addColumn<QByteArrayList>("lines");
QTest::addColumn<QHttpHeaderMap>("headers");
QTest::addColumn<QHttpEngine::Socket::HeaderMap>("headers");
QTest::newRow("empty line")
<< false
@@ -142,21 +180,21 @@ void TestQHttpParser::testParseHeaderList_data()
<< headers;
}
void TestQHttpParser::testParseHeaderList()
void TestParser::testParseHeaderList()
{
QFETCH(bool, success);
QFETCH(QByteArrayList, lines);
QHttpHeaderMap outHeaders;
QCOMPARE(QHttpParser::parseHeaderList(lines, outHeaders), success);
QHttpEngine::Socket::HeaderMap outHeaders;
QCOMPARE(QHttpEngine::Parser::parseHeaderList(lines, outHeaders), success);
if(success) {
QFETCH(QHttpHeaderMap, headers);
if (success) {
QFETCH(QHttpEngine::Socket::HeaderMap, headers);
QCOMPARE(outHeaders, headers);
}
}
void TestQHttpParser::testParseHeaders_data()
void TestParser::testParseHeaders_data()
{
QTest::addColumn<bool>("success");
QTest::addColumn<QByteArray>("data");
@@ -172,27 +210,27 @@ void TestQHttpParser::testParseHeaders_data()
<< (QByteArrayList() << "GET" << "/" << "HTTP/1.0");
}
void TestQHttpParser::testParseHeaders()
void TestParser::testParseHeaders()
{
QFETCH(bool, success);
QFETCH(QByteArray, data);
QByteArrayList outParts;
QHttpHeaderMap outHeaders;
QHttpEngine::Socket::HeaderMap outHeaders;
QCOMPARE(QHttpParser::parseHeaders(data, outParts, outHeaders), success);
QCOMPARE(QHttpEngine::Parser::parseHeaders(data, outParts, outHeaders), success);
if(success) {
if (success) {
QFETCH(QByteArrayList, parts);
QCOMPARE(outParts, parts);
}
}
void TestQHttpParser::testParseRequestHeaders_data()
void TestParser::testParseRequestHeaders_data()
{
QTest::addColumn<bool>("success");
QTest::addColumn<QByteArray>("data");
QTest::addColumn<QByteArray>("method");
QTest::addColumn<QHttpEngine::Socket::Method>("method");
QTest::addColumn<QByteArray>("path");
QTest::newRow("bad HTTP version")
@@ -202,23 +240,23 @@ void TestQHttpParser::testParseRequestHeaders_data()
QTest::newRow("GET request")
<< true
<< QByteArray("GET / HTTP/1.0")
<< QByteArray("GET")
<< QHttpEngine::Socket::GET
<< QByteArray("/");
}
void TestQHttpParser::testParseRequestHeaders()
void TestParser::testParseRequestHeaders()
{
QFETCH(bool, success);
QFETCH(QByteArray, data);
QByteArray outMethod;
QHttpEngine::Socket::Method outMethod;
QByteArray outPath;
QHttpHeaderMap outHeaders;
QHttpEngine::Socket::HeaderMap outHeaders;
QCOMPARE(QHttpParser::parseRequestHeaders(data, outMethod, outPath, outHeaders), success);
QCOMPARE(QHttpEngine::Parser::parseRequestHeaders(data, outMethod, outPath, outHeaders), success);
if(success) {
QFETCH(QByteArray, method);
if (success) {
QFETCH(QHttpEngine::Socket::Method, method);
QFETCH(QByteArray, path);
QCOMPARE(method, outMethod);
@@ -226,7 +264,7 @@ void TestQHttpParser::testParseRequestHeaders()
}
}
void TestQHttpParser::testParseResponseHeaders_data()
void TestParser::testParseResponseHeaders_data()
{
QTest::addColumn<bool>("success");
QTest::addColumn<QByteArray>("data");
@@ -244,18 +282,18 @@ void TestQHttpParser::testParseResponseHeaders_data()
<< QByteArray("NOT FOUND");
}
void TestQHttpParser::testParseResponseHeaders()
void TestParser::testParseResponseHeaders()
{
QFETCH(bool, success);
QFETCH(QByteArray, data);
int outStatusCode;
QByteArray outStatusReason;
QHttpHeaderMap outHeaders;
QHttpEngine::Socket::HeaderMap outHeaders;
QCOMPARE(QHttpParser::parseResponseHeaders(data, outStatusCode, outStatusReason, outHeaders), success);
QCOMPARE(QHttpEngine::Parser::parseResponseHeaders(data, outStatusCode, outStatusReason, outHeaders), success);
if(success) {
if (success) {
QFETCH(int, statusCode);
QFETCH(QByteArray, statusReason);
@@ -264,5 +302,5 @@ void TestQHttpParser::testParseResponseHeaders()
}
}
QTEST_MAIN(TestQHttpParser)
#include "TestQHttpParser.moc"
QTEST_MAIN(TestParser)
#include "TestParser.moc"

View File

@@ -0,0 +1,85 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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.
*/
#include <QHostAddress>
#include <QObject>
#include <QTest>
#include <qhttpengine/server.h>
#include <qhttpengine/socket.h>
#include <qhttpengine/qobjecthandler.h>
#include <qhttpengine/proxyhandler.h>
#include "common/qsimplehttpclient.h"
#include "common/qsocketpair.h"
const QString Path = "test";
const QByteArray Data = "test";
class TestProxyHandler : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testDataPassthrough();
};
void TestProxyHandler::testDataPassthrough()
{
// Create the upstream handler (simple echo)
QHttpEngine::QObjectHandler upstreamHandler;
upstreamHandler.registerMethod(Path, [](QHttpEngine::Socket *socket) {
socket->write(socket->readAll());
socket->close();
}, true);
// Create the upstream server and begin listening
QHttpEngine::Server upstreamServer(&upstreamHandler);
QVERIFY(upstreamServer.listen(QHostAddress::LocalHost));
// Create the proxy handler
QHttpEngine::ProxyHandler handler(upstreamServer.serverAddress(), upstreamServer.serverPort());
QSocketPair pair;
QTRY_VERIFY(pair.isConnected());
QSimpleHttpClient client(pair.client());
QHttpEngine::Socket *socket = new QHttpEngine::Socket(pair.server(), &pair);
// Send the headers and wait for them to be parsed
QHttpEngine::Socket::HeaderMap headers{
{"Content-Length", QByteArray::number(Data.length())}
};
client.sendHeaders("POST", QString("/%1").arg(Path).toUtf8(), headers);
QTRY_VERIFY(socket->isHeadersParsed());
// Route the request (triggering the upstream connection)
handler.route(socket, Path);
// Send the data and wait for it to return
client.sendData(Data);
QTRY_COMPARE(client.data(), Data);
}
QTEST_MAIN(TestProxyHandler)
#include "TestProxyHandler.moc"

View File

@@ -1,130 +0,0 @@
/*
* Copyright (c) 2015 Nathan Osman
*
* 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.
*/
#include <QDir>
#include <QFile>
#include <QObject>
#include <QSignalSpy>
#include <QTemporaryDir>
#include <QTest>
#include <QHttpEngine/QHttpSocket>
#include <QHttpEngine/QFilesystemHandler>
#include "common/qsimplehttpclient.h"
#include "common/qsocketpair.h"
const QByteArray Data = "test";
class TestQFilesystemHandler : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void testRequests_data();
void testRequests();
private:
bool createFile(const QString &path);
bool createDirectory(const QString &path);
QTemporaryDir dir;
};
void TestQFilesystemHandler::initTestCase()
{
QVERIFY(createFile("outside"));
QVERIFY(createDirectory("root"));
QVERIFY(createFile("root/inside"));
}
void TestQFilesystemHandler::testRequests_data()
{
QTest::addColumn<QString>("path");
QTest::addColumn<int>("statusCode");
QTest::addColumn<QByteArray>("data");
QTest::newRow("nonexistent resource")
<< "nonexistent"
<< static_cast<int>(QHttpSocket::NotFound)
<< QByteArray();
QTest::newRow("outside document root")
<< "../outside"
<< static_cast<int>(QHttpSocket::NotFound)
<< QByteArray();
QTest::newRow("inside document root")
<< "inside"
<< static_cast<int>(QHttpSocket::OK)
<< Data;
QTest::newRow("directory listing")
<< ""
<< static_cast<int>(QHttpSocket::OK)
<< QByteArray();
}
void TestQFilesystemHandler::testRequests()
{
QFETCH(QString, path);
QFETCH(int, statusCode);
QFETCH(QByteArray, data);
QFilesystemHandler handler(QDir(dir.path()).absoluteFilePath("root"));
QSocketPair pair;
QTRY_VERIFY(pair.isConnected());
QSimpleHttpClient client(pair.client());
QHttpSocket socket(pair.server(), &pair);
handler.route(&socket, path);
QTRY_COMPARE(client.statusCode(), statusCode);
if(!data.isNull()) {
QTRY_COMPARE(client.data(), data);
}
}
bool TestQFilesystemHandler::createFile(const QString &path)
{
QFile file(QDir(dir.path()).absoluteFilePath(path));
if(!file.open(QIODevice::WriteOnly)) {
return false;
}
return file.write(Data) == Data.length();
}
bool TestQFilesystemHandler::createDirectory(const QString &path)
{
return QDir(dir.path()).mkpath(path);
}
QTEST_MAIN(TestQFilesystemHandler)
#include "TestQFilesystemHandler.moc"

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -27,11 +27,11 @@
#include <QTcpSocket>
#include <QTest>
#include <QHttpEngine/QIODeviceCopier>
#include <qhttpengine/qiodevicecopier.h>
#include "common/qsocketpair.h"
const QByteArray SampleData = "1234567890";
const QByteArray SampleData = "1234567890123456789012345678901234567890";
class TestQIODeviceCopier : public QObject
{
@@ -39,6 +39,9 @@ class TestQIODeviceCopier : public QObject
private Q_SLOTS:
void testRange_data();
void testRange();
void testQBuffer();
void testQTcpSocket();
void testStop();
@@ -52,7 +55,7 @@ void TestQIODeviceCopier::testQBuffer()
QByteArray destData;
QBuffer dest(&destData);
QIODeviceCopier copier(&src, &dest);
QHttpEngine::QIODeviceCopier copier(&src, &dest);
copier.setBufferSize(2);
QSignalSpy errorSpy(&copier, SIGNAL(error(QString)));
@@ -73,7 +76,7 @@ void TestQIODeviceCopier::testQTcpSocket()
QByteArray destData;
QBuffer dest(&destData);
QIODeviceCopier copier(pair.server(), &dest);
QHttpEngine::QIODeviceCopier copier(pair.server(), &dest);
copier.setBufferSize(2);
QSignalSpy errorSpy(&copier, SIGNAL(error(QString)));
@@ -97,7 +100,7 @@ void TestQIODeviceCopier::testStop()
QByteArray destData;
QBuffer dest(&destData);
QIODeviceCopier copier(pair.server(), &dest);
QHttpEngine::QIODeviceCopier copier(pair.server(), &dest);
copier.start();
@@ -110,5 +113,47 @@ void TestQIODeviceCopier::testStop()
QTRY_COMPARE(destData, SampleData);
}
void TestQIODeviceCopier::testRange_data()
{
QTest::addColumn<int>("from");
QTest::addColumn<int>("to");
QTest::addColumn<int>("bufferSize");
QTest::newRow("range: 1-21, bufSize: 8")
<< 1 << 21 << 8;
QTest::newRow("range: 0-21, bufSize: 7")
<< 0 << 21 << 7;
QTest::newRow("range: 10-, bufSize: 5")
<< 10 << -1 << 5;
}
void TestQIODeviceCopier::testRange()
{
QFETCH(int, from);
QFETCH(int, to);
QFETCH(int, bufferSize);
QBuffer src;
src.setData(SampleData);
QByteArray destData;
QBuffer dest(&destData);
QHttpEngine::QIODeviceCopier copier(&src, &dest);
copier.setBufferSize(bufferSize);
copier.setRange(from, to);
QSignalSpy errorSpy(&copier, SIGNAL(error(QString)));
QSignalSpy finishedSpy(&copier, SIGNAL(finished()));
copier.start();
QTRY_COMPARE(finishedSpy.count(), 1);
QCOMPARE(errorSpy.count(), 0);
QCOMPARE(destData, SampleData.mid(from, to - from + 1));
}
QTEST_MAIN(TestQIODeviceCopier)
#include "TestQIODeviceCopier.moc"

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -20,27 +20,25 @@
* IN THE SOFTWARE.
*/
#include <QJsonDocument>
#include <QJsonObject>
#include <QObject>
#include <QTest>
#include <QVariantMap>
#include <QHttpEngine/QHttpSocket>
#include <QHttpEngine/QObjectHandler>
#include <qhttpengine/socket.h>
#include <qhttpengine/qobjecthandler.h>
#include "common/qsimplehttpclient.h"
#include "common/qsocketpair.h"
class DummyHandler : public QObjectHandler
class DummyAPI : public QObject
{
Q_OBJECT
private Q_SLOTS:
public Q_SLOTS:
void invalidSignature(QVariantMap) {}
QVariantMap validSlot(QVariantMap params) {
return params;
void wrongArgumentCount() {}
void wrongArgumentType(int) {}
void valid(QHttpEngine::Socket *socket) {
socket->writeError(QHttpEngine::Socket::OK);
}
};
@@ -50,85 +48,84 @@ class TestQObjectHandler : public QObject
private Q_SLOTS:
void testRequests_data();
void testRequests();
void testOldConnection_data();
void testOldConnection();
void testNewConnection();
};
void TestQObjectHandler::testRequests_data()
void TestQObjectHandler::testOldConnection_data()
{
QTest::addColumn<QByteArray>("method");
QTest::addColumn<QByteArray>("path");
QTest::addColumn<QByteArray>("data");
QTest::addColumn<QByteArray>("slot");
QTest::addColumn<int>("statusCode");
QVariantMap map;
map.insert("param1", 1);
map.insert("param2", 2);
QTest::newRow("invalid slot")
<< QByteArray(SLOT(invalid()))
<< static_cast<int>(QHttpEngine::Socket::InternalServerError);
QByteArray data = QJsonDocument(QJsonObject::fromVariantMap(map)).toJson();
QTest::newRow("wrong argument count")
<< QByteArray(SLOT(wrongArgumentCount()))
<< static_cast<int>(QHttpEngine::Socket::InternalServerError);
QTest::newRow("nonexistent slot")
<< QByteArray("POST")
<< QByteArray("nonexistent")
<< data
<< static_cast<int>(QHttpSocket::NotFound);
QTest::newRow("wrong argument type")
<< QByteArray(SLOT(wrongArgumentType(int)))
<< static_cast<int>(QHttpEngine::Socket::InternalServerError);
QTest::newRow("invalid signature")
<< QByteArray("POST")
<< QByteArray("invalidSignature")
<< data
<< static_cast<int>(QHttpSocket::InternalServerError);
QTest::newRow("bad method")
<< QByteArray("GET")
<< QByteArray("validSlot")
<< data
<< static_cast<int>(QHttpSocket::MethodNotAllowed);
QTest::newRow("malformed JSON")
<< QByteArray("POST")
<< QByteArray("validSlot")
<< QByteArray("")
<< static_cast<int>(QHttpSocket::BadRequest);
QTest::newRow("valid slot")
<< QByteArray("POST")
<< QByteArray("validSlot")
<< data
<< static_cast<int>(QHttpSocket::OK);
QTest::newRow("valid")
<< QByteArray(SLOT(valid(QHttpEngine::Socket*)))
<< static_cast<int>(QHttpEngine::Socket::OK);
}
void TestQObjectHandler::testRequests()
void TestQObjectHandler::testOldConnection()
{
QFETCH(QByteArray, method);
QFETCH(QByteArray, path);
QFETCH(QByteArray, data);
QFETCH(QByteArray, slot);
QFETCH(int, statusCode);
DummyHandler handler;
QHttpEngine::QObjectHandler handler;
DummyAPI api;
handler.registerMethod("test", &api, slot.constData());
QSocketPair pair;
QTRY_VERIFY(pair.isConnected());
QSimpleHttpClient client(pair.client());
QHttpSocket socket(pair.server(), &pair);
QHttpEngine::Socket *socket = new QHttpEngine::Socket(pair.server(), &pair);
QHttpHeaderMap headers;
headers.insert("Content-Length", QByteArray::number(data.length()));
client.sendHeaders(method, path, headers);
client.sendData(data);
QTRY_VERIFY(socket.isHeadersParsed());
handler.route(&socket, socket.path());
client.sendHeaders("GET", "test");
QTRY_VERIFY(socket->isHeadersParsed());
handler.route(socket, socket->path());
QTRY_COMPARE(client.statusCode(), statusCode);
}
if(statusCode == QHttpSocket::OK) {
QVERIFY(client.headers().contains("Content-Length"));
QTRY_COMPARE(client.data().length(), client.headers().value("Content-Length").toInt());
QCOMPARE(QJsonDocument::fromJson(client.data()), QJsonDocument::fromJson(data));
void TestQObjectHandler::testNewConnection()
{
QHttpEngine::QObjectHandler handler;
DummyAPI api;
// Connect to object slot
handler.registerMethod("0", &api, &DummyAPI::valid);
// Connect to functor
handler.registerMethod("1", [](QHttpEngine::Socket *socket) {
socket->writeError(QHttpEngine::Socket::OK);
});
handler.registerMethod("2", &api, [](QHttpEngine::Socket *socket) {
socket->writeError(QHttpEngine::Socket::OK);
});
for (int i = 0; i < 3; ++i) {
QSocketPair pair;
QTRY_VERIFY(pair.isConnected());
QSimpleHttpClient client(pair.client());
QHttpEngine::Socket *socket = new QHttpEngine::Socket(pair.server(), &pair);
client.sendHeaders("GET", QByteArray::number(i));
QTRY_VERIFY(socket->isHeadersParsed());
handler.route(socket, socket->path());
QTRY_COMPARE(client.statusCode(), static_cast<int>(QHttpEngine::Socket::OK));
}
}

290
tests/TestRange.cpp Normal file
View File

@@ -0,0 +1,290 @@
/*
* Copyright (c) 2017 Aleksei Ermakov
*
* 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.
*/
#include <QObject>
#include <QString>
#include <QTest>
#include <qhttpengine/range.h>
class TestRange : public QObject
{
Q_OBJECT
public:
TestRange();
private Q_SLOTS:
void testDefaultConstructor();
void testAssignmentOperator();
void testFromToLength_data();
void testFromToLength();
void testIsValid_data();
void testIsValid();
void testParseFromString_data();
void testParseFromString();
void testContentRange_data();
void testContentRange();
};
TestRange::TestRange()
{
}
void TestRange::testDefaultConstructor()
{
QHttpEngine::Range range;
QCOMPARE(range.isValid(), false);
}
void TestRange::testAssignmentOperator()
{
QHttpEngine::Range range;
QHttpEngine::Range otherRange(100, 200, -1);
range = otherRange;
QCOMPARE(range.isValid(), true);
QCOMPARE(range.from(), 100);
QCOMPARE(range.to(), 200);
}
void TestRange::testFromToLength_data()
{
QTest::addColumn<int>("inFrom");
QTest::addColumn<int>("inTo");
QTest::addColumn<int>("inDataSize");
QTest::addColumn<int>("from");
QTest::addColumn<int>("to");
QTest::addColumn<int>("length");
QTest::newRow("Last 500 bytes")
<< -500 << -1 << -1
<< -500 << -1 << 500;
QTest::newRow("Last 500 bytes with 800 dataSize")
<< -500 << -1 << 800
<< 300 << 799 << 500;
QTest::newRow("Skip first 10 bytes")
<< 10 << -1 << -1
<< 10 << -1 << -1;
QTest::newRow("Skip first 10 bytes with 100 dataSize")
<< 10 << -1 << 100
<< 10 << 99 << 90;
}
void TestRange::testFromToLength()
{
QFETCH(int, inFrom);
QFETCH(int, inTo);
QFETCH(int, inDataSize);
QFETCH(int, from);
QFETCH(int, to);
QFETCH(int, length);
QHttpEngine::Range range(inFrom, inTo, inDataSize);
QCOMPARE(range.from(), from);
QCOMPARE(range.to(), to);
QCOMPARE(range.length(), length);
}
void TestRange::testIsValid_data()
{
QTest::addColumn<int>("from");
QTest::addColumn<int>("to");
QTest::addColumn<int>("dataSize");
QTest::addColumn<bool>("valid");
QTest::newRow("Normal range")
<< 0 << 100 << -1 << true;
QTest::newRow("Normal range with 'dataSize'")
<< 0 << 99 << 100 << true;
QTest::newRow("Last N bytes")
<< -500 << -1 << -1 << true;
QTest::newRow("Last N bytes with 'dataSize'")
<< -500 << -1 << 500 << true;
QTest::newRow("Skip first N bytes")
<< 10 << -1 << -1 << true;
QTest::newRow("Skip first N bytes with 'dataSize'")
<< 10 << -1 << 500 << true;
QTest::newRow("OutOfBounds 'to' > 'from'")
<< 100 << 50 << -1 << false;
QTest::newRow("OutOfBounds 'from' > 'dataSize'")
<< 100 << 200 << 150 << false;
QTest::newRow("Last N bytes where N > 'dataSize'")
<< -500 << -1 << 499 << false;
QTest::newRow("Skip first N bytes where N > 'dataSize'")
<< 500 << -1 << 499 << false;
}
void TestRange::testIsValid()
{
QFETCH(int, from);
QFETCH(int, to);
QFETCH(int, dataSize);
QFETCH(bool, valid);
QHttpEngine::Range range(from, to, dataSize);
QCOMPARE(range.isValid(), valid);
}
void TestRange::testParseFromString_data()
{
QTest::addColumn<QString>("data");
QTest::addColumn<int>("dataSize");
QTest::addColumn<bool>("valid");
QTest::addColumn<int>("from");
QTest::addColumn<int>("to");
QTest::addColumn<int>("length");
QTest::newRow("Normal range")
<< "0-99" << -1
<< true << 0 << 99 << 100;
QTest::newRow("Normal range with 'dataSize'")
<< "0-99" << 100
<< true << 0 << 99 << 100;
QTest::newRow("Last N bytes")
<< "-256" << -1
<< true << -256 << -1 << 256;
QTest::newRow("Last N bytes with 'dataSize'")
<< "-256" << 256
<< true << 0 << 255 << 256;
QTest::newRow("Skip first N bytes")
<< "100-" << -1
<< true << 100 << -1 << -1;
QTest::newRow("Skip first N bytes with 'dataSize'")
<< "100-" << 200
<< true << 100 << 199 << 100;
QTest::newRow("OutOfBounds 'to' > 'from'")
<< "100-50" << -1
<< false;
QTest::newRow("OutOfBounds 'from' > 'dataSize'")
<< "0-200" << 100
<< false;
QTest::newRow("Last N bytes where N > 'dataSize'")
<< "-500" << 200
<< false;
QTest::newRow("Skip first N bytes where N > 'dataSize'")
<< "100-" << 100
<< false;
QTest::newRow("Bad input: '-'")
<< "-" << -1
<< false;
QTest::newRow("Bad input: 'abc-def'")
<< "abc-def" << -1
<< false;
QTest::newRow("Bad input: 'abcdef'")
<< "abcdef" << -1
<< false;
}
void TestRange::testParseFromString()
{
QFETCH(QString, data);
QFETCH(int, dataSize);
QFETCH(bool, valid);
QHttpEngine::Range range(data, dataSize);
QCOMPARE(range.isValid(), valid);
if (valid) {
QFETCH(int, from);
QFETCH(int, to);
QFETCH(int, length);
QCOMPARE(range.from(), from);
QCOMPARE(range.to(), to);
QCOMPARE(range.length(), length);
}
}
void TestRange::testContentRange_data()
{
QTest::addColumn<int>("from");
QTest::addColumn<int>("to");
QTest::addColumn<int>("dataSize");
QTest::addColumn<QString>("contentRange");
QTest::newRow("Normal range with 'dataSize'")
<< 0 << 100 << 1000
<< "0-100/1000";
QTest::newRow("Normal range without 'dataSize'")
<< 0 << 100 << -1
<< "0-100/*";
QTest::newRow("Invalid range with 'dataSize'")
<< 100 << 10 << 1200
<< "*/1200";
QTest::newRow("Invalid range without 'dataSize'")
<< 100 << 10 << -1
<< "";
}
void TestRange::testContentRange()
{
QFETCH(int, from);
QFETCH(int, to);
QFETCH(int, dataSize);
QFETCH(QString, contentRange);
QHttpEngine::Range range(from, to, dataSize);
QCOMPARE(range.contentRange(), contentRange);
}
QTEST_MAIN(TestRange)
#include "TestRange.moc"

114
tests/TestServer.cpp Normal file
View File

@@ -0,0 +1,114 @@
/*
* Copyright (c) 2017 Nathan Osman
*
* 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.
*/
#include <QTcpSocket>
#include <QTest>
#if !defined(QT_NO_SSL)
# include <QFile>
# include <QSslCertificate>
# include <QSslConfiguration>
# include <QSslKey>
# include <QSslSocket>
#endif
#include <qhttpengine/server.h>
#include <qhttpengine/handler.h>
#include "common/qsimplehttpclient.h"
class TestHandler : public QHttpEngine::Handler
{
Q_OBJECT
public:
virtual void process(QHttpEngine::Socket *socket, const QString &path) {
mPath = path;
socket->deleteLater();
}
QString mPath;
};
class TestServer : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testServer();
#if !defined(QT_NO_SSL)
void testSsl();
#endif
};
void TestServer::testServer()
{
TestHandler handler;
QHttpEngine::Server server(&handler);
QVERIFY(server.listen(QHostAddress::LocalHost));
QTcpSocket socket;
socket.connectToHost(server.serverAddress(), server.serverPort());
QTRY_COMPARE(socket.state(), QAbstractSocket::ConnectedState);
QSimpleHttpClient client(&socket);
client.sendHeaders("GET", "/test");
QTRY_COMPARE(handler.mPath, QString("test"));
}
#if !defined(QT_NO_SSL)
void TestServer::testSsl()
{
QFile keyFile(":/key.pem");
QVERIFY(keyFile.open(QIODevice::ReadOnly));
QSslKey key(&keyFile, QSsl::Rsa);
QList<QSslCertificate> certs = QSslCertificate::fromPath(":/cert.pem");
QSslConfiguration config;
config.setPrivateKey(key);
config.setLocalCertificateChain(certs);
QHttpEngine::Server server;
server.setSslConfiguration(config);
QVERIFY(server.listen(QHostAddress::LocalHost));
QSslSocket socket;
socket.setCaCertificates(certs);
socket.connectToHost(server.serverAddress(), server.serverPort());
socket.setPeerVerifyName("localhost");
QTRY_COMPARE(socket.state(), QAbstractSocket::ConnectedState);
socket.startClientEncryption();
QTRY_VERIFY(socket.isEncrypted());
}
#endif
QTEST_MAIN(TestServer)
#include "TestServer.moc"

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -20,16 +20,20 @@
* IN THE SOFTWARE.
*/
#include <QJsonDocument>
#include <QJsonObject>
#include <QObject>
#include <QSignalSpy>
#include <QTest>
#include <QHttpEngine/QHttpSocket>
#include <QHttpEngine/QHttpParser>
#include <qhttpengine/socket.h>
#include <qhttpengine/parser.h>
#include "common/qsimplehttpclient.h"
#include "common/qsocketpair.h"
Q_DECLARE_METATYPE(QHttpEngine::Socket::QueryStringMap)
// Utility macro (avoids duplication) that creates a pair of connected
// sockets, a QSimpleHttpClient for the client and a QHttpSocket for the
// server
@@ -37,7 +41,7 @@
QSocketPair pair; \
QTRY_VERIFY(pair.isConnected()); \
QSimpleHttpClient client(pair.client()); \
QHttpSocket server(pair.server())
QHttpEngine::Socket *server = new QHttpEngine::Socket(pair.server(), &pair);
const QByteArray Method = "POST";
const QByteArray Path = "/test";
@@ -45,13 +49,13 @@ const int StatusCode = 404;
const QByteArray StatusReason = "NOT FOUND";
const QByteArray Data = "test";
class TestQHttpSocket : public QObject
class TestSocket : public QObject
{
Q_OBJECT
public:
TestQHttpSocket();
TestSocket();
private Q_SLOTS:
@@ -59,76 +63,78 @@ private Q_SLOTS:
void testData();
void testRedirect();
void testSignals();
void testJson();
private:
QHttpHeaderMap headers;
QHttpEngine::Socket::HeaderMap headers;
};
TestQHttpSocket::TestQHttpSocket()
TestSocket::TestSocket()
{
headers.insert("Content-Type", "text/plain");
headers.insert("Content-Length", QByteArray::number(Data.length()));
}
void TestQHttpSocket::testProperties()
void TestSocket::testProperties()
{
CREATE_SOCKET_PAIR();
client.sendHeaders(Method, Path, headers);
QTRY_COMPARE(server.method(), Method);
QCOMPARE(server.path(), Path);
QCOMPARE(server.headers(), headers);
QTRY_VERIFY(server->isHeadersParsed());
QCOMPARE(server->method(), QHttpEngine::Socket::POST);
QCOMPARE(server->rawPath(), Path);
QCOMPARE(server->headers(), headers);
server.setStatusCode(StatusCode, StatusReason);
server.setHeaders(headers);
server.writeHeaders();
server->setStatusCode(StatusCode, StatusReason);
server->setHeaders(headers);
server->writeHeaders();
QTRY_COMPARE(client.statusCode(), StatusCode);
QCOMPARE(client.statusReason(), StatusReason);
QCOMPARE(client.headers(), headers);
}
void TestQHttpSocket::testData()
void TestSocket::testData()
{
CREATE_SOCKET_PAIR();
client.sendHeaders(Method, Path, headers);
client.sendData(Data);
QTRY_COMPARE(server.contentLength(), Data.length());
QTRY_COMPARE(server.bytesAvailable(), Data.length());
QCOMPARE(server.readAll(), Data);
QTRY_COMPARE(server->contentLength(), Data.length());
QTRY_COMPARE(server->bytesAvailable(), Data.length());
QCOMPARE(server->readAll(), Data);
server.writeHeaders();
server.write(Data);
server->writeHeaders();
server->write(Data);
QTRY_COMPARE(client.data(), Data);
}
void TestQHttpSocket::testRedirect()
void TestSocket::testRedirect()
{
CREATE_SOCKET_PAIR();
QSignalSpy disconnectedSpy(pair.client(), SIGNAL(disconnected()));
server.writeRedirect(Path, true);
server->writeRedirect(Path, true);
QTRY_COMPARE(client.statusCode(), static_cast<int>(QHttpSocket::MovedPermanently));
QTRY_COMPARE(client.statusCode(), static_cast<int>(QHttpEngine::Socket::MovedPermanently));
QCOMPARE(client.headers().value("Location"), Path);
QTRY_COMPARE(disconnectedSpy.count(), 1);
}
void TestQHttpSocket::testSignals()
void TestSocket::testSignals()
{
CREATE_SOCKET_PAIR();
QSignalSpy headersParsedSpy(&server, SIGNAL(headersParsed()));
QSignalSpy readyReadSpy(&server, SIGNAL(readyRead()));
QSignalSpy readChannelFinishedSpy(&server, SIGNAL(readChannelFinished()));
QSignalSpy bytesWrittenSpy(&server, SIGNAL(bytesWritten(qint64)));
QSignalSpy aboutToCloseSpy(&server, SIGNAL(aboutToClose()));
QSignalSpy headersParsedSpy(server, SIGNAL(headersParsed()));
QSignalSpy readyReadSpy(server, SIGNAL(readyRead()));
QSignalSpy bytesWrittenSpy(server, SIGNAL(bytesWritten(qint64)));
QSignalSpy aboutToCloseSpy(server, SIGNAL(aboutToClose()));
QSignalSpy readChannelFinishedSpy(server, SIGNAL(readChannelFinished()));
client.sendHeaders(Method, Path, headers);
@@ -137,26 +143,48 @@ void TestQHttpSocket::testSignals()
client.sendData(Data);
QTRY_COMPARE(server.bytesAvailable(), Data.length());
QTRY_COMPARE(server->bytesAvailable(), Data.length());
QVERIFY(readyReadSpy.count() > 0);
QCOMPARE(readChannelFinishedSpy.count(), 1);
server.writeHeaders();
server.write(Data);
server->writeHeaders();
server->write(Data);
QTRY_COMPARE(client.data().length(), Data.length());
QVERIFY(bytesWrittenSpy.count() > 0);
qint64 bytesWritten = 0;
for(int i = 0; i < bytesWrittenSpy.count(); ++i) {
for (int i = 0; i < bytesWrittenSpy.count(); ++i) {
bytesWritten += bytesWrittenSpy.at(i).at(0).toLongLong();
}
QCOMPARE(bytesWritten, Data.length());
QTRY_COMPARE(aboutToCloseSpy.count(), 0);
server.close();
server->close();
QTRY_COMPARE(aboutToCloseSpy.count(), 1);
QCOMPARE(readChannelFinishedSpy.count(), 1);
}
QTEST_MAIN(TestQHttpSocket)
#include "TestQHttpSocket.moc"
void TestSocket::testJson()
{
CREATE_SOCKET_PAIR();
QJsonObject object{{"a", "b"}, {"c", 123}};
QByteArray data = QJsonDocument(object).toJson();
client.sendHeaders(Method, Path, QHttpEngine::Socket::HeaderMap{
{"Content-Length", QByteArray::number(data.length())},
{"Content-Type", "application/json"}
});
client.sendData(data);
QTRY_VERIFY(server->isHeadersParsed());
QTRY_VERIFY(server->bytesAvailable() >= server->contentLength());
QJsonDocument document;
QVERIFY(server->readJson(document));
QCOMPARE(document.object(), object);
}
QTEST_MAIN(TestSocket)
#include "TestSocket.moc"

24
tests/cert.pem Normal file
View File

@@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIID9DCCAtygAwIBAgIJAPW70SPs8X5bMA0GCSqGSIb3DQEBBQUAMFkxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xNjEwMjkwODE0
NTJaFw0yNjEwMjcwODE0NTJaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21l
LVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV
BAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOUk
YyqLadcQcBenI/G0IPwPf7N/Sir1R3RibUsJFrdbI5adfL5eeuWEYpXAfeprv6Jm
5jWKslZZ8rZz8UOslFSB61ecOJmhJflBRbl+JAQFz65mmepUHoQ5dKKyCC/GDUYn
ohA8mNb4UVpMsna2zX/0bHDDi8D9uJpuI0s9cX2RHZYqA515xRKIv2RdigYFQQo+
eKV2EhgYONMcyWl1n8+kKCj5SCQ4lETT6u2wlF5w2xTbo+ppwAz/muDyFxSo869K
5c+majOcaYa0GWufay5sSCuqSrwI2mwOF3xaOurBf8f7DEcKUZ/K5SdpiDHKFt41
adPFif84wi+Q78ni/5sCAwEAAaOBvjCBuzAdBgNVHQ4EFgQUh4OKP3/x2xokzeI5
yWzfbefrBCIwgYsGA1UdIwSBgzCBgIAUh4OKP3/x2xokzeI5yWzfbefrBCKhXaRb
MFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJ
bnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMTCWxvY2FsaG9zdIIJAPW7
0SPs8X5bMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHVh9WFavc0l
Pwx/GjXg2lh1KQ5t8IAGL+5aDZUTtA143PQeudf0Qv0bSNETMr+AxSKkz+WIZaCO
ZSvkFsfIreaKzOVzoORiedta+y5gvPIv5pzdFfYPHaVqomqyaTMUM+p1d4uj2xy3
P+oq1i8EnTGOF86meMtr/NW5+f67QBdtdWfS+2ephAf31Rchl+aOp8uUoMfDn/kU
BrQd7vo6vsvuo6pKWoOFQXQpPGFT0rKpF5G6gs6CCxGffv2+ABotK1CL5DpR3zGq
WdbuQR7P510y5/ojYgWXJJ8K05V4ORVPnwFZ2NnX1QqnKXHnUm2hmsXM+gIcMW2B
O6+dyYko8Kk=
-----END CERTIFICATE-----

View File

@@ -4,6 +4,8 @@ set(SRC
)
add_library(common STATIC ${SRC})
target_link_libraries(common QHttpEngine)
qt5_use_modules(common Network)
set_target_properties(common PROPERTIES
CXX_STANDARD 11
CXX_STANDARD_REQUIRED ON
)
target_link_libraries(common Qt5::Network qhttpengine)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -33,10 +33,10 @@ QSimpleHttpClient::QSimpleHttpClient(QTcpSocket *socket)
onReadyRead();
}
void QSimpleHttpClient::sendHeaders(const QByteArray &method, const QByteArray &path, const QHttpHeaderMap &headers)
void QSimpleHttpClient::sendHeaders(const QByteArray &method, const QByteArray &path, const QHttpEngine::Socket::HeaderMap &headers)
{
QByteArray data = method + " " + path + " HTTP/1.0\r\n";
for(QHttpHeaderMap::const_iterator i = headers.constBegin(); i != headers.constEnd(); ++i) {
for (auto i = headers.constBegin(); i != headers.constEnd(); ++i) {
data.append(i.key() + ": " + i.value() + "\r\n");
}
data.append("\r\n");
@@ -51,18 +51,24 @@ void QSimpleHttpClient::sendData(const QByteArray &data)
void QSimpleHttpClient::onReadyRead()
{
if(mHeadersParsed) {
if (mHeadersParsed) {
mData.append(mSocket->readAll());
} else {
mBuffer.append(mSocket->readAll());
// Parse the headers if the double CRLF sequence was found
int index = mBuffer.indexOf("\r\n\r\n");
if(index != -1) {
QHttpParser::parseResponseHeaders(mBuffer.left(index), mStatusCode, mStatusReason, mHeaders);
if (index != -1) {
QHttpEngine::Parser::parseResponseHeaders(mBuffer.left(index), mStatusCode, mStatusReason, mHeaders);
mHeadersParsed = true;
mData.append(mBuffer.mid(index + 4));
}
}
}
bool QSimpleHttpClient::isDataReceived() const
{
return mHeaders.contains("Content-Length") &&
mData.length() >= mHeaders.value("Content-Length").toInt();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
@@ -27,7 +27,8 @@
#include <QObject>
#include <QTcpSocket>
#include <QHttpEngine/QHttpParser>
#include <qhttpengine/parser.h>
#include <qhttpengine/socket.h>
/**
* @brief Simple HTTP client for testing purposes
@@ -44,7 +45,7 @@ public:
QSimpleHttpClient(QTcpSocket *socket);
void sendHeaders(const QByteArray &method, const QByteArray &path, const QHttpHeaderMap &headers);
void sendHeaders(const QByteArray &method, const QByteArray &path, const QHttpEngine::Socket::HeaderMap &headers = QHttpEngine::Socket::HeaderMap());
void sendData(const QByteArray &data);
int statusCode() const {
@@ -55,7 +56,7 @@ public:
return mStatusReason;
}
QHttpHeaderMap headers() const {
QHttpEngine::Socket::HeaderMap headers() const {
return mHeaders;
}
@@ -63,6 +64,8 @@ public:
return mData;
}
bool isDataReceived() const;
private Q_SLOTS:
void onReadyRead();
@@ -76,7 +79,7 @@ private:
int mStatusCode;
QByteArray mStatusReason;
QHttpHeaderMap mHeaders;
QHttpEngine::Socket::HeaderMap mHeaders;
QByteArray mData;
};

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015 Nathan Osman
* Copyright (c) 2017 Nathan Osman
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to

Some files were not shown because too many files have changed in this diff Show More