Compare commits
125 Commits
0.1.0
...
43f55df516
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43f55df516 | ||
|
|
f70405e4d6 | ||
|
|
4b2705fb32 | ||
|
|
b85aa4b246 | ||
|
|
d05bdd3ec2 | ||
|
|
e01c0f23c0 | ||
|
|
ada6c53130 | ||
|
|
c06432caef | ||
|
|
f56b19513b | ||
|
|
c2b21cdca4 | ||
|
|
9652e8f508 | ||
|
|
8b3fc48108 | ||
|
|
83f8113a05 | ||
|
|
65c30c9017 | ||
|
|
a30530b21a | ||
|
|
40345c81b2 | ||
|
|
5fcfb11e73 | ||
|
|
0a7b81d4ba | ||
|
|
461d5100dc | ||
|
|
96a714c909 | ||
|
|
7e962c8ac4 | ||
|
|
255200ab15 | ||
|
|
c769cee616 | ||
|
|
ad01f31cf2 | ||
|
|
c0c7d88366 | ||
|
|
25ea46861f | ||
|
|
77fb7c4d71 | ||
|
|
76a9bc171b | ||
|
|
2c48893ec7 | ||
|
|
da7eb850e4 | ||
|
|
784e220276 | ||
|
|
2df0aa0709 | ||
|
|
5c57fda4a8 | ||
|
|
bd7740bbc5 | ||
|
|
a77badb827 | ||
|
|
91ca9c82f7 | ||
|
|
61d219721e | ||
|
|
6fea4fbd61 | ||
|
|
4866f85380 | ||
|
|
396168a0dd | ||
|
|
fd7e7e9603 | ||
|
|
2fdf8fac71 | ||
|
|
e68c0b0f11 | ||
|
|
0197444a83 | ||
|
|
aa6650f6df | ||
|
|
82a5043649 | ||
|
|
8e13f1644a | ||
|
|
eca6097858 | ||
|
|
88169f590e | ||
|
|
ac6249445b | ||
|
|
d73fbe09aa | ||
|
|
018989c081 | ||
|
|
998416abae | ||
|
|
a9d1891f9a | ||
|
|
1972d82809 | ||
|
|
bead7a68e7 | ||
|
|
c48f20c264 | ||
|
|
3c662ab005 | ||
|
|
58c5dd786c | ||
|
|
bde806b289 | ||
|
|
48967c398b | ||
|
|
ab5282c976 | ||
|
|
4a2fc923f0 | ||
|
|
927d6436d1 | ||
|
|
651fb87314 | ||
|
|
0c22530acc | ||
|
|
8ca3666112 | ||
|
|
49aa771b63 | ||
|
|
1633a085c7 | ||
|
|
79da42f0b9 | ||
|
|
f454b0be7e | ||
|
|
c3fc9fd400 | ||
|
|
cf3932529a | ||
|
|
482a67733f | ||
|
|
f059e9bc66 | ||
|
|
4dd17ad4ea | ||
|
|
7e7e6945a0 | ||
|
|
4cfbec7c6c | ||
|
|
5968739f03 | ||
|
|
c1fc8a115d | ||
|
|
e8b076bdc4 | ||
|
|
1604bcf279 | ||
|
|
6edf9f4c89 | ||
|
|
6df88ad4aa | ||
|
|
e35fdbde78 | ||
|
|
1c1c1e7752 | ||
|
|
642ea69ffc | ||
|
|
8304e03c6d | ||
|
|
0708516e28 | ||
|
|
17e8b4e8aa | ||
|
|
7e7ec8c7af | ||
|
|
f7290e0cb9 | ||
|
|
a700e854b8 | ||
|
|
7ac8fa1c88 | ||
|
|
7c3a13e89d | ||
|
|
409ba4be75 | ||
|
|
27cc231218 | ||
|
|
f73a7659f1 | ||
|
|
bf4ca27cc7 | ||
|
|
b901757731 | ||
|
|
1bea4efab7 | ||
|
|
00fab6b5e1 | ||
|
|
d20c1d33a9 | ||
|
|
337d021936 | ||
|
|
9dd1072bc3 | ||
|
|
7c460edb57 | ||
|
|
7930ef4a50 | ||
|
|
c6cb5d274b | ||
|
|
b39b18be6c | ||
|
|
287edf9825 | ||
|
|
61bfd8c70a | ||
|
|
feaab366ef | ||
|
|
7edb26c433 | ||
|
|
391dea5ddb | ||
|
|
dafff09b5d | ||
|
|
7ea1f703db | ||
|
|
e83dd36bfd | ||
|
|
7cea9352f3 | ||
|
|
56c37275cd | ||
|
|
27ed1bbe4a | ||
|
|
30099e78ea | ||
|
|
3cf92f2373 | ||
|
|
7554193a16 | ||
|
|
dd748698a3 | ||
|
|
f1ab4f40b9 |
21
.travis.yml
Normal file
21
.travis.yml
Normal 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
|
||||
@@ -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)
|
||||
|
||||
36
README.md
36
README.md
@@ -1,40 +1,12 @@
|
||||
## QHTTPEngine
|
||||
|
||||
[](https://snap-ci.com/nitroshare/qhttpengine/branch/master)
|
||||
[](https://travis-ci.org/nitroshare/qhttpengine)
|
||||
[](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/
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
398
doc/Doxyfile.in
398
doc/Doxyfile.in
@@ -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.
|
||||
|
||||
97
doc/index.md
97
doc/index.md
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
21
examples/auth/CMakeLists.txt
Normal file
21
examples/auth/CMakeLists.txt
Normal 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
76
examples/auth/client.cpp
Normal 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();
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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 ¶ms)
|
||||
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 ¶ms)
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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 ¶ms);
|
||||
QVariantMap getMessages(const QVariantMap ¶ms);
|
||||
void messages(QHttpEngine::Socket *socket);
|
||||
void messagesNew(QHttpEngine::Socket *socket);
|
||||
|
||||
private:
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ body {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.messages {
|
||||
.container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
});
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
#include "qfilesystemhandler.h"
|
||||
@@ -1 +0,0 @@
|
||||
#include "qhttphandler.h"
|
||||
@@ -1 +0,0 @@
|
||||
#include "qhttpparser.h"
|
||||
@@ -1 +0,0 @@
|
||||
#include "qhttpserver.h"
|
||||
@@ -1 +0,0 @@
|
||||
#include "qhttpsocket.h"
|
||||
@@ -1 +0,0 @@
|
||||
#include "qibytearray.h"
|
||||
@@ -1 +0,0 @@
|
||||
#include "qiodevicecopier.h"
|
||||
@@ -1 +0,0 @@
|
||||
#include "qlocalfile.h"
|
||||
@@ -1 +0,0 @@
|
||||
#include "qobjecthandler.h"
|
||||
@@ -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
|
||||
@@ -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
|
||||
87
src/include/qhttpengine/basicauthmiddleware.h
Normal file
87
src/include/qhttpengine/basicauthmiddleware.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
102
src/include/qhttpengine/ibytearray.h
Normal file
102
src/include/qhttpengine/ibytearray.h
Normal 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
|
||||
106
src/include/qhttpengine/localauthmiddleware.h
Normal file
106
src/include/qhttpengine/localauthmiddleware.h
Normal 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
|
||||
@@ -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
|
||||
64
src/include/qhttpengine/middleware.h
Normal file
64
src/include/qhttpengine/middleware.h
Normal 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
|
||||
@@ -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
|
||||
65
src/include/qhttpengine/proxyhandler.h
Normal file
65
src/include/qhttpengine/proxyhandler.h
Normal 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
|
||||
@@ -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
|
||||
196
src/include/qhttpengine/qobjecthandler.h
Normal file
196
src/include/qhttpengine/qobjecthandler.h
Normal 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
|
||||
200
src/include/qhttpengine/range.h
Normal file
200
src/include/qhttpengine/range.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
45
src/qhttpengine_export.h.in
Normal file
45
src/qhttpengine_export.h.in
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
77
src/src/basicauthmiddleware.cpp
Normal file
77
src/src/basicauthmiddleware.cpp
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
91
src/src/localauthmiddleware.cpp
Normal file
91
src/src/localauthmiddleware.cpp
Normal 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;
|
||||
}
|
||||
53
src/src/localauthmiddleware_p.h
Normal file
53
src/src/localauthmiddleware_p.h
Normal 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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
50
src/src/proxyhandler.cpp
Normal 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);
|
||||
}
|
||||
@@ -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
160
src/src/proxysocket.cpp
Normal 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
71
src/src/proxysocket.h
Normal 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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
110
src/src/qobjecthandler.cpp
Normal 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));
|
||||
}
|
||||
77
src/src/qobjecthandler_p.h
Normal file
77
src/src/qobjecthandler_p.h
Normal 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
284
src/src/range.cpp
Normal 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);
|
||||
}
|
||||
@@ -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
113
src/src/server.cpp
Normal 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
63
src/src/server_p.h
Normal 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
397
src/src/socket.cpp
Normal 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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
119
tests/TestBasicAuthMiddleware.cpp
Normal file
119
tests/TestBasicAuthMiddleware.cpp
Normal 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"
|
||||
212
tests/TestFilesystemHandler.cpp
Normal file
212
tests/TestFilesystemHandler.cpp
Normal 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"
|
||||
@@ -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
69
tests/TestIByteArray.cpp
Normal 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"
|
||||
92
tests/TestLocalAuthMiddleware.cpp
Normal file
92
tests/TestLocalAuthMiddleware.cpp
Normal 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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
85
tests/TestProxyHandler.cpp
Normal file
85
tests/TestProxyHandler.cpp
Normal 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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
|
||||
@@ -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
290
tests/TestRange.cpp
Normal 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
114
tests/TestServer.cpp
Normal 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"
|
||||
@@ -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
24
tests/cert.pem
Normal 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-----
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user