silvioprog 5 лет назад
Родитель
Сommit
c67acfa1b8
48 измененных файлов с 929 добавлено и 1060 удалено
  1. 4 4
      .github/ISSUE_TEMPLATE/bug_report.md
  2. 2 2
      .github/ISSUE_TEMPLATE/feature_request.md
  3. 10 0
      .github/ISSUE_TEMPLATE/project_using_sagui.md
  4. 2 2
      .github/ISSUE_TEMPLATE/question.md
  5. 0 648
      .gitignore
  6. 7 6
      .markdownlint.json
  7. 9 2
      CMakeLists.txt
  8. 14 9
      README.md
  9. 1 1
      cmake/SgMHD.cmake
  10. 5 4
      cmake/SgSummary.cmake
  11. 2 2
      doxygen/example_httpcomp.h
  12. 35 0
      doxygen/example_httpreq_isolate.h
  13. 35 0
      doxygen/example_httpsrv_sse.h
  14. 22 13
      examples/CMakeLists.txt
  15. 77 0
      examples/example_httpreq_isolate.c
  16. 12 15
      examples/example_httpsrv_benchmark.c
  17. 110 0
      examples/example_httpsrv_sse.c
  18. 6 5
      examples/example_httpuplds.c
  19. 59 22
      include/sagui.h
  20. 15 14
      src/CMakeLists.txt
  21. 3 3
      src/inet.h
  22. 4 4
      src/sg_extra.c
  23. 3 3
      src/sg_extra.h
  24. 59 4
      src/sg_httpreq.c
  25. 16 5
      src/sg_httpreq.h
  26. 24 12
      src/sg_httpres.c
  27. 3 3
      src/sg_httpres.h
  28. 88 15
      src/sg_httpsrv.c
  29. 8 0
      src/sg_httpsrv.h
  30. 38 38
      src/sg_macros.h
  31. 3 3
      src/sg_router.c
  32. 4 4
      src/sg_routes.c
  33. 2 2
      src/sg_str.c
  34. 1 1
      src/sg_strmap.c
  35. 31 24
      src/sg_utils.c
  36. 13 7
      src/sg_utils.h
  37. 20 13
      test/CMakeLists.txt
  38. 10 9
      test/sg_assert.h
  39. 4 4
      test/test_extra.c
  40. 45 7
      test/test_httpreq.c
  41. 8 80
      test/test_httpres.c
  42. 29 9
      test/test_httpsrv.c
  43. 55 30
      test/test_httpsrv_curl.c
  44. 2 2
      test/test_httpsrv_tls_curl.c
  45. 16 16
      test/test_httpuplds.c
  46. 4 4
      test/test_routes.c
  47. 2 2
      test/test_str.c
  48. 7 7
      test/test_utils.c

+ 4 - 4
.github/ISSUE_TEMPLATE/bug_report.md

@@ -19,8 +19,8 @@ Raised error and/or generated log.
 
 OS, compiler and Sagui version to reproduce the problem, e.g.:
 
-- openSUSE Tumbleweed
-- GCC N.N.N
-- Sagui N.N.N x86
+- Fedora32
+- Clang N.N.N
+- Sagui N.N.N
 
-Any other information here. If applicable, a screenshot to help explain the problem.
+Any other information here. If applicable, a screenshot to help explain the problem.

+ 2 - 2
.github/ISSUE_TEMPLATE/feature_request.md

@@ -1,10 +1,10 @@
 ---
 name: Feature request
-about: Suggest an idea for this project
+about: Suggest a feature for this project
 labels: enhancement
 
 ---
 
 A clear and concise description of what should be improved in Sagui library.
 
-Any links, projects or drafts can be referenced if they could help to explain the feature.
+Any links, projects or drafts can be referenced if they could help to explain the feature.

+ 10 - 0
.github/ISSUE_TEMPLATE/project_using_sagui.md

@@ -0,0 +1,10 @@
+---
+name: Project using Sagui
+about: Free or commercial project using Sagui library
+labels: documentation
+
+---
+
+A description/link and the license of the project, e.g.:
+
+[Brook framework](https://github.com/risoflora/brookframework) - Pascal framework which helps to develop web applications. [[LGPL v2.1](https://github.com/risoflora/brookframework/blob/master/LICENSE)]

+ 2 - 2
.github/ISSUE_TEMPLATE/question.md

@@ -1,8 +1,8 @@
 ---
 name: Question
-about: The issue tracker is not for questions. Please ask questions on https://stackoverflow.com/questions/tagged/libsagui.
+about: Please ask questions on https://stackoverflow.com/questions/tagged/libsagui.
 labels: question
 
 ---
 
-If you have any question about a feature, example, documentation etc., please ask it on https://stackoverflow.com/questions/tagged/libsagui.
+If you have any question about a feature, example, documentation etc., please ask it at https://stackoverflow.com/questions/tagged/libsagui.

+ 0 - 648
.gitignore

@@ -1,650 +1,2 @@
-# Created by .ignore support plugin (hsz.mobi)
-### Delphi template
-# Uncomment these types if you want even more clean repository. But be careful.
-# It can make harm to an existing project source. Read explanations below.
-#
-# Resource files are binaries containing manifest, project icon and version info.
-# They can not be viewed as text or compared by diff-tools. Consider replacing them with .rc files.
-#*.res
-#
-# Type library file (binary). In old Delphi versions it should be stored.
-# Since Delphi 2009 it is produced from .ridl file and can safely be ignored.
-#*.tlb
-#
-# Diagram Portfolio file. Used by the diagram editor up to Delphi 7.
-# Uncomment this if you are not using diagrams or use newer Delphi version.
-#*.ddp
-#
-# Visual LiveBindings file. Added in Delphi XE2.
-# Uncomment this if you are not using LiveBindings Designer.
-#*.vlb
-#
-# Deployment Manager configuration file for your project. Added in Delphi XE2.
-# Uncomment this if it is not mobile development and you do not use remote debug feature.
-#*.deployproj
-#
-# C++ object files produced when C/C++ Output file generation is configured.
-# Uncomment this if you are not using external objects (zlib library for example).
-#*.obj
-#
-
-# Delphi compiler-generated binaries (safe to delete)
-*.exe
-*.dll
-*.bpl
-*.bpi
-*.dcp
-*.so
-*.apk
-*.drc
-*.map
-*.dres
-*.rsm
-*.tds
-*.dcu
-*.lib
-*.a
-*.o
-*.ocx
-
-# Delphi autogenerated files (duplicated info)
-*.cfg
-*.hpp
-*Resource.rc
-
-# Delphi local files (user-specific info)
-*.local
-*.identcache
-*.projdata
-*.tvsconfig
-*.dsk
-
-# Delphi history and backups
-__history/
-__recovery/
-*.~*
-
-# Castalia statistics file (since XE7 Castalia is distributed with Delphi)
-*.stat
-### Archives template
-# It's better to unpack these files and commit the raw source because
-# git has its own built in compression methods.
-*.7z
-*.jar
-*.rar
-*.tgz
-*.bzip
-*.bz2
-*.xz
-*.lzma
-*.cab
-
-# Packing-only formats
-*.iso
-*.tar
-
-# Package management formats
-*.dmg
-*.xpi
-*.gem
-*.egg
-*.deb
-*.rpm
-*.msi
-*.msm
-*.msp
-### JetBrains template
-# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
-# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
-
-# User-specific stuff:
-.idea/**/tasks.xml
-.idea/dictionaries
-
-# Sensitive or high-churn files:
-.idea/**/dataSources/
-.idea/**/dataSources.ids
-.idea/**/dataSources.xml
-.idea/**/dataSources.local.xml
-.idea/**/sqlDataSources.xml
-.idea/**/dynamic.xml
-.idea/**/uiDesigner.xml
-
-# Gradle:
-.idea/**/gradle.xml
-.idea/**/libraries
-
-# CMake
 build/
-cmake-build-*
-
-# Mongo Explorer plugin:
-.idea/**/mongoSettings.xml
-
-## File-based project format:
-*.iws
-
-## Plugin-specific files:
-
-# IntelliJ
-out/
-
-# mpeltonen/sbt-idea plugin
-.idea_modules/
-
-# JIRA plugin
-atlassian-ide-plugin.xml
-
-# Cursive Clojure plugin
-.idea/replstate.xml
-
-# Crashlytics plugin (for Android Studio and IntelliJ)
-com_crashlytics_export_strings.xml
-crashlytics.properties
-crashlytics-build.properties
-fabric.properties
-### Lazarus template
-# Lazarus compiler-generated binaries (safe to delete)
-*.dylib
-*.lrs
-*.res
-*.compiled
-*.dbg
-*.ppu
-*.or
-
-# Lazarus autogenerated files (duplicated info)
-*.rst
-*.rsj
-*.lrt
-
-# Lazarus local files (user-specific info)
-*.lps
-
-# Lazarus backups and unit output folders.
-# These can be changed by user in Lazarus/project options.
-backup/
-*.bak
 lib/
-
-# Application bundle for Mac OS
-*.app/
-### CMake template
-CMakeScripts
-Testing
-install_manifest.txt
-compile_commands.json
-CTestTestfile.cmake
-### C template
-# Prerequisites
-*.d
-
-# Object files
-*.ko
-*.obj
-*.elf
-
-# Linker output
-*.ilk
-*.exp
-
-# Precompiled Headers
-*.gch
-*.pch
-
-# Libraries
-*.la
-*.lo
-
-# Shared objects (inc. Windows DLLs)
-*.so.*
-
-# Executables
-*.app
-*.i*86
-*.x86_64
-*.hex
-
-# Debug files
-*.dSYM/
-*.su
-*.idb
-*.pdb
-
-# Kernel Module Compile Results
-*.mod*
-*.cmd
-.tmp_versions/
-modules.order
-Module.symvers
-Mkfile.old
-dkms.conf
-### C++ template
-# Prerequisites
-
-# Compiled Object files
-*.slo
-
-# Precompiled Headers
-
-# Compiled Dynamic libraries
-
-# Fortran module files
-*.mod
-*.smod
-
-# Compiled Static libraries
-*.lai
-
-# Executables
-### VisualStudio template
-## Ignore Visual Studio temporary files, build results, and
-## files generated by popular Visual Studio add-ons.
-##
-## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
-
-# User-specific files
-*.suo
-*.user
-*.userosscache
-*.sln.docstates
-
-# User-specific files (MonoDevelop/Xamarin Studio)
-*.userprefs
-
-# Build results
-[Dd]ebug/
-[Dd]ebugPublic/
-[Rr]elease/
-[Rr]eleases/
-x64/
-x86/
-bld/
-[Bb]in/
-[Oo]bj/
-[Ll]og/
-
-# Visual Studio 2015/2017 cache/options directory
-.vs/
-# Uncomment if you have tasks that create the project's static files in wwwroot
-#wwwroot/
-
-# Visual Studio 2017 auto generated files
-Generated\ Files/
-
-# MSTest test Results
-[Tt]est[Rr]esult*/
-[Bb]uild[Ll]og.*
-
-# NUNIT
-*.VisualState.xml
-TestResult.xml
-
-# Build Results of an ATL Project
-[Dd]ebugPS/
-[Rr]eleasePS/
-dlldata.c
-
-# Benchmark Results
-BenchmarkDotNet.Artifacts/
-
-# .NET Core
-project.lock.json
-project.fragment.lock.json
-artifacts/
-**/Properties/launchSettings.json
-
-# StyleCop
-StyleCopReport.xml
-
-# Files built by Visual Studio
-*_i.c
-*_p.c
-*_i.h
-*.meta
-*.pgc
-*.pgd
-*.rsp
-*.sbr
-*.tlb
-*.tli
-*.tlh
-*.tmp
-*.tmp_proj
-*.vspscc
-*.vssscc
-.builds
-*.pidb
-*.svclog
-*.scc
-
-# Chutzpah Test files
-_Chutzpah*
-
-# Visual C++ cache files
-ipch/
-*.aps
-*.ncb
-*.opendb
-*.opensdf
-*.sdf
-*.cachefile
-*.VC.db
-*.VC.VC.opendb
-
-# Visual Studio profiler
-*.psess
-*.vsp
-*.vspx
-*.sap
-
-# Visual Studio Trace Files
-*.e2e
-
-# TFS 2012 Local Workspace
-$tf/
-
-# Guidance Automation Toolkit
-*.gpState
-
-# ReSharper is a .NET coding add-in
-_ReSharper*/
-*.[Rr]e[Ss]harper
-*.DotSettings.user
-
-# JustCode is a .NET coding add-in
-.JustCode
-
-# TeamCity is a build add-in
-_TeamCity*
-
-# DotCover is a Code Coverage Tool
-*.dotCover
-
-# AxoCover is a Code Coverage Tool
-.axoCover/*
-!.axoCover/settings.json
-
-# Visual Studio code coverage results
-*.coverage
-*.coveragexml
-
-# NCrunch
-_NCrunch_*
-.*crunch*.local.xml
-nCrunchTemp_*
-
-# MightyMoose
-*.mm.*
-AutoTest.Net/
-
-# Web workbench (sass)
-.sass-cache/
-
-# Installshield output folder
-[Ee]xpress/
-
-# DocProject is a documentation generator add-in
-DocProject/buildhelp/
-DocProject/Help/*.HxT
-DocProject/Help/*.HxC
-DocProject/Help/*.hhc
-DocProject/Help/*.hhk
-DocProject/Help/*.hhp
-DocProject/Help/Html2
-DocProject/Help/html
-
-# Click-Once directory
-publish/
-
-# Publish Web Output
-*.[Pp]ublish.xml
-*.azurePubxml
-# Note: Comment the next line if you want to checkin your web deploy settings,
-# but database connection strings (with potential passwords) will be unencrypted
-*.pubxml
-*.publishproj
-
-# Microsoft Azure Web App publish settings. Comment the next line if you want to
-# checkin your Azure Web App publish settings, but sensitive information contained
-# in these scripts will be unencrypted
-PublishScripts/
-
-# NuGet Packages
-*.nupkg
-# The packages folder can be ignored because of Package Restore
-**/[Pp]ackages/*
-# except build/, which is used as an MSBuild target.
-!**/[Pp]ackages/build/
-# Uncomment if necessary however generally it will be regenerated when needed
-#!**/[Pp]ackages/repositories.config
-# NuGet v3's project.json files produces more ignorable files
-*.nuget.props
-*.nuget.targets
-
-# Microsoft Azure Build Output
-csx/
-*.build.csdef
-
-# Microsoft Azure Emulator
-ecf/
-rcf/
-
-# Windows Store app package directories and files
-AppPackages/
-BundleArtifacts/
-Package.StoreAssociation.xml
-_pkginfo.txt
-*.appx
-
-# Visual Studio cache files
-# files ending in .cache can be ignored
-*.[Cc]ache
-# but keep track of directories ending in .cache
-!*.[Cc]ache/
-
-# Others
-ClientBin/
-~$*
-*~
-*.dbmdl
-*.dbproj.schemaview
-*.jfm
-*.pfx
-*.publishsettings
-orleans.codegen.cs
-
-# Including strong name files can present a security risk
-# (https://github.com/github/gitignore/pull/2483#issue-259490424)
-#*.snk
-
-# Since there are multiple workflows, uncomment next line to ignore bower_components
-# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
-#bower_components/
-
-# RIA/Silverlight projects
-Generated_Code/
-
-# Backup & report files from converting an old project file
-# to a newer Visual Studio version. Backup files are not needed,
-# because we have git ;-)
-_UpgradeReport_Files/
-Backup*/
-UpgradeLog*.XML
-UpgradeLog*.htm
-
-# SQL Server files
-*.mdf
-*.ldf
-*.ndf
-
-# Business Intelligence projects
-*.rdl.data
-*.bim.layout
-*.bim_*.settings
-
-# Microsoft Fakes
-FakesAssemblies/
-
-# GhostDoc plugin setting file
-*.GhostDoc.xml
-
-# Node.js Tools for Visual Studio
-.ntvs_analysis.dat
-node_modules/
-
-# TypeScript v1 declaration files
-typings/
-
-# Visual Studio 6 build log
-*.plg
-
-# Visual Studio 6 workspace options file
-*.opt
-
-# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
-*.vbw
-
-# Visual Studio LightSwitch build output
-**/*.HTMLClient/GeneratedArtifacts
-**/*.DesktopClient/GeneratedArtifacts
-**/*.DesktopClient/ModelManifest.xml
-**/*.Server/GeneratedArtifacts
-**/*.Server/ModelManifest.xml
-_Pvt_Extensions
-
-# Paket dependency manager
-.paket/paket.exe
-paket-files/
-
-# FAKE - F# Make
-.fake/
-
-# JetBrains Rider
-.idea/
-*.sln.iml
-
-# CodeRush
-.cr/
-
-# Python Tools for Visual Studio (PTVS)
-__pycache__/
-*.pyc
-
-# Cake - Uncomment if you are using it
-# tools/**
-# !tools/packages.config
-
-# Tabs Studio
-*.tss
-
-# Telerik's JustMock configuration file
-*.jmconfig
-
-# BizTalk build output
-*.btp.cs
-*.btm.cs
-*.odx.cs
-*.xsd.cs
-
-# OpenCover UI analysis results
-OpenCover/
-
-# Azure Stream Analytics local run output
-ASALocalRun/
-
-# MSBuild Binary and Structured Log
-*.binlog
-
-### Autotools template
-# http://www.gnu.org/software/automake
-
-Makefile.in
-/ar-lib
-/mdate-sh
-/py-compile
-/test-driver
-/ylwrap
-
-# http://www.gnu.org/software/autoconf
-
-/autom4te.cache
-/autoscan.log
-/autoscan-*.log
-/aclocal.m4
-/compile
-/config.guess
-/config.h.in
-/config.sub
-/configure
-/configure.scan
-/depcomp
-/install-sh
-/missing
-/stamp-h1
-
-# https://www.gnu.org/software/libtool/
-
-/ltmain.sh
-
-# http://www.gnu.org/software/texinfo
-
-/texinfo.tex
-
-# http://www.gnu.org/software/m4/
-
-m4/libtool.m4
-m4/ltoptions.m4
-m4/ltsugar.m4
-m4/ltversion.m4
-m4/lt~obsolete.m4
-autom4te.cache
-### Linux template
-
-# temporary files which can be created if a process still has a handle open of a deleted file
-.fuse_hidden*
-
-# KDE directory preferences
-.directory
-
-# Linux trash folder which might appear on any partition or disk
-.Trash-*
-
-# .nfs files are created when an open file is removed but is still being accessed
-.nfs*
-### Windows template
-# Windows thumbnail cache files
-Thumbs.db
-ehthumbs.db
-ehthumbs_vista.db
-
-# Dump file
-*.stackdump
-
-# Folder config file
-[Dd]esktop.ini
-
-# Recycle Bin used on file shares
-$RECYCLE.BIN/
-
-# Windows Installer files
-
-# Windows shortcuts
-*.lnk
-### Gcov template
-# gcc coverage testing tool files
-
-*.gcno
-*.gcda
-*.gcov
-
-### Doxygen generated files
-
-doc/html/
-doc/pdf/
-
-# Temporary files
-foo.txt
-bar.txt
-foo_uploaded.txt
-bar_uploaded.txt

+ 7 - 6
.markdownlint.json

@@ -1,7 +1,8 @@
 {
-    "default": true,
-    "MD013": false,
-    "MD025": false,
-    "MD026": false,
-    "MD040": false
-}
+  "default": true,
+  "MD013": false,
+  "MD025": false,
+  "MD026": false,
+  "MD040": false,
+  "MD041": false
+}

+ 9 - 2
CMakeLists.txt

@@ -4,7 +4,7 @@
 #
 # Main library building.
 #
-# The main building of the Sagui library. It includes all necessary sub-bulding
+# The main building of the Sagui library. It includes all necessary sub-building
 # scripts to manage the library building.
 #
 # ::
@@ -21,7 +21,7 @@
 #
 # Cross-platform library which helps to develop web servers or frameworks.
 #
-# Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+# Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
 #
 # Sagui library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -64,6 +64,13 @@ if((SG_USE_OUTPUT_PREFIX OR MINGW)
       CACHE PATH "..." FORCE)
 endif()
 
+# Based on: https://gitlab.kitware.com/cmake/cmake/issues/19460
+if(DEFINED CMAKE_OSX_SYSROOT)
+  set(CC "CC=${CMAKE_C_COMPILER} ${CMAKE_C_SYSROOT_FLAG} ${CMAKE_OSX_SYSROOT}")
+else()
+  set(CC "CC=${CMAKE_C_COMPILER}")
+endif()
+
 if("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")
   set(CMAKE_COMPILER_IS_CLANG ON)
 endif()

+ 14 - 9
README.md

@@ -1,6 +1,8 @@
+[![License: LGPL v2.1](https://img.shields.io/badge/License-LGPL%20v2.1-lemmon.svg)](https://github.com/risoflora/libsagui/blob/master/LICENSE)
 [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/2140/badge)](https://bestpractices.coreinfrastructure.org/projects/2140)
 [![Language grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/risoflora/libsagui.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/risoflora/libsagui/context:cpp)
-[![Build Status](https://travis-ci.org/risoflora/libsagui.svg?branch=master)](https://travis-ci.org/risoflora/libsagui)
+[![GitHub releases](https://img.shields.io/github/v/release/risoflora/libsagui?color=lemmon)](https://github.com/risoflora/libsagui/releases)
+[![Build status](https://travis-ci.org/risoflora/libsagui.svg?branch=master)](https://travis-ci.org/risoflora/libsagui)
 
 # Overview
 
@@ -9,9 +11,10 @@ Sagui is a cross-platform C library which helps to develop web servers or framew
 # Features
 
 - **Requests processing through:**
-  - Event-driven - _single-thread + main loop + select/epoll_.
-  - Threaded - _one thread per request_.
-  - Thread pool - _thread pool + select/epoll_.
+  - Event-driven - single-thread + polling.
+  - Threaded - one thread per request.
+  - Polling - pre-allocated threads.
+  - Isolated request - request processed outside main thread.
 - **High-performance path routing that supports:**
   - Regular expressions using [PCRE2](https://www.pcre.org/current/doc/html/pcre2pattern.html) [syntax](https://www.pcre.org/current/doc/html/pcre2syntax.html).
   - Just-in-time optimization ([JIT](https://www.pcre.org/current/doc/html/pcre2jit.html)).
@@ -38,7 +41,7 @@ Sagui is a cross-platform C library which helps to develop web servers or framew
 
 # Examples
 
-A minimal HTTP server example:
+A minimal `hello worl` HTTP server:
 
 ```c
 void req_cb(void *cls, struct sg_httpreq *req, struct sg_httpres *res) {
@@ -55,7 +58,7 @@ int main() {
 }
 ```
 
-The router support is isolated from the HTTP, so it can be used to route any path structure, for example:
+The router support is isolated from the HTTP feature, so it can be used to route any path structure, for example:
 
 ```c
 void home_cb(void *cls, struct sg_route *route) {
@@ -138,7 +141,7 @@ The documentation has been written in [Doxygen](https://www.stack.nl/~dimitri/do
 
 # Downloading
 
-All stable releases are available for download at the [releases page](https://github.com/risoflora/libsagui/releases). For Windows, the packages `libsagui-N.N.N-dll.zip` (and their respective GPG signature) contains the compiled DLLs for 32 and 64 bits. For other systems, the packages `Source code (tar.gz|zip)` contains the library source.
+All stable binaries are available for download at the [releases page](https://github.com/risoflora/libsagui/releases) with their respective checksums. For other systems, the packages `Source code (tar.gz|zip)` contains the library source.
 
 # Building/installing
 
@@ -154,11 +157,13 @@ See also [Checking backward API/ABI compatibility of Sagui library versions](htt
 
 # Projects using Sagui
 
-- [Brook framework](https://github.com/risoflora/brookframework) - Pascal framework which helps to develop web applications.
+- [Brook framework](https://github.com/risoflora/brookframework) - Pascal framework which helps to develop web applications. [[LGPL v2.1](https://github.com/risoflora/brookframework/blob/master/LICENSE)]
+
+Would you like to add your project to that list above? Feel free to open a [new issue](https://github.com/risoflora/libsagui/issues/new?labels=documentation&template=project_using_sagui.md) requesting it! :-)
 
 # Contributing
 
-Sagui is totally open source and would not be possible without our [contributors](https://github.com/risoflora/libsagui/blob/master/THANKS). If you want to submit contributions, please fork the project on GitHub and send a pull request. You retain the copyright on your contributions. If you have questions, open a new issue at the [issues page](https://github.com/risoflora/libsagui/issues). For donations to support this project, please click the botton below.
+Sagui is totally open source and would not be possible without our [contributors](https://github.com/risoflora/libsagui/blob/master/THANKS). If you want to submit contributions, please fork the project on GitHub and send a pull request. You retain the copyright on your contributions. If you have questions, open a new issue at the [issues page](https://github.com/risoflora/libsagui/issues). For donations to support this project, please click the button below.
 
 [![Support this project](https://www.paypalobjects.com/en_US/GB/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=silvioprog%40gmail%2ecom&lc=US&item_name=libsagui&item_number=libsagui&currency_code=USD&bn=PP%2dDonationsBF%3aproject%2dsupport%2ejpg%3aNonHosted)
 

+ 1 - 1
cmake/SgMHD.cmake

@@ -95,7 +95,7 @@ ExternalProject_Add(
   PREFIX ${CMAKE_BINARY_DIR}/${MHD_FULL_NAME}
   SOURCE_DIR ${CMAKE_SOURCE_DIR}/lib/${MHD_FULL_NAME}
   CONFIGURE_COMMAND
-    <SOURCE_DIR>/configure ${_manifest_tool} --host=${CMAKE_C_MACHINE}
+    <SOURCE_DIR>/configure ${CC} ${_manifest_tool} --host=${CMAKE_C_MACHINE}
     --prefix=<INSTALL_DIR> ${MHD_OPTIONS}
   BUILD_COMMAND ${CMAKE_MAKE_PROGRAM}
   INSTALL_COMMAND ${CMAKE_MAKE_PROGRAM} install

+ 5 - 4
cmake/SgSummary.cmake

@@ -148,15 +148,16 @@ Sagui library ${VERSION} - building summary:
   Generator: ${CMAKE_GENERATOR}
   Install: ${CMAKE_INSTALL_PREFIX}
   System: ${_system_name}
-  Compiler:
+  Compiler details:
     Executable: ${CMAKE_C_COMPILER}
     Version: ${CMAKE_C_COMPILER_VERSION}
     Machine: ${CMAKE_C_MACHINE}
     CFLAGS: ${_cflags}
   Build: ${_build_type}-${_build_arch} (${_lib_type})
-  HTTPS: ${_https_support}
-  Compression: ${_http_compression}
-  Routing: ${_routing}
+  Additional features:
+    HTTPS support: ${_https_support}
+    HTTP compression: ${_http_compression}
+    Path routing: ${_routing}
   Examples: ${_build_examples}
   Docs: ${_build_html}
   Run tests: ${_build_testing}

+ 2 - 2
doxygen/example_httpcomp.h

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -29,7 +29,7 @@
 
 /**
  * \example example_httpcomp.c
- * Example drastically simplified to show the basic concept of HTTP compression by deflate.
+ * Example simplified to show the basic concept of HTTP compression by deflate.
  */
 
 #endif /* EXAMPLE_HTTPCOMP_H */

+ 35 - 0
doxygen/example_httpreq_isolate.h

@@ -0,0 +1,35 @@
+/*                         _
+ *   ___  __ _  __ _ _   _(_)
+ *  / __|/ _` |/ _` | | | | |
+ *  \__ \ (_| | (_| | |_| | |
+ *  |___/\__,_|\__, |\__,_|_|
+ *             |___/
+ *
+ * Cross-platform library which helps to develop web servers or frameworks.
+ *
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
+ *
+ * Sagui library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * Sagui library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sagui library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef EXAMPLE_HTTPREQ_ISOLATE_H
+#define EXAMPLE_HTTPREQ_ISOLATE_H
+
+/**
+ * \example example_httpreq_isolate.c
+ * Simple example showing how to isolate requests on their own threads.
+ */
+
+#endif /* EXAMPLE_HTTPREQ_ISOLATE_H */

+ 35 - 0
doxygen/example_httpsrv_sse.h

@@ -0,0 +1,35 @@
+/*                         _
+ *   ___  __ _  __ _ _   _(_)
+ *  / __|/ _` |/ _` | | | | |
+ *  \__ \ (_| | (_| | |_| | |
+ *  |___/\__,_|\__, |\__,_|_|
+ *             |___/
+ *
+ * Cross-platform library which helps to develop web servers or frameworks.
+ *
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
+ *
+ * Sagui library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * Sagui library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sagui library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef EXAMPLE_HTTPSRV_SSE_H
+#define EXAMPLE_HTTPSRV_SSE_H
+
+/**
+ * \example example_httpsrv.c
+ * Simple example showing a basic SSE (Server-sent events) server.
+ */
+
+#endif /* EXAMPLE_HTTPSRV_SSE_H */

+ 22 - 13
examples/CMakeLists.txt

@@ -25,7 +25,7 @@
 #
 # Cross-platform library which helps to develop web servers or frameworks.
 #
-# Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+# Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
 #
 # Sagui library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -55,15 +55,18 @@ if(SG_BUILD_EXAMPLES)
   endif()
   set(SG_EXAMPLES_SOURCE_DIR ${CMAKE_SOURCE_DIR}/examples)
   list(
-    APPEND SG_EXAMPLES
-           str
-           strmap
-           httpauth
-           httpcookie
-           httpsrv
-           httpuplds
-           httpsrv_benchmark
-           httpreq_payload)
+    APPEND
+    SG_EXAMPLES
+    str
+    strmap
+    httpauth
+    httpcookie
+    httpsrv
+    httpuplds
+    httpsrv_benchmark
+    httpsrv_sse
+    httpreq_payload
+    httpreq_isolate)
   if(SG_HTTPS_SUPPORT AND GNUTLS_FOUND)
     set(SG_EXAMPLES_CERTS_DIR "${SG_EXAMPLES_SOURCE_DIR}/certs")
     add_definitions(-DSG_EXAMPLES_CERTS_DIR="${SG_EXAMPLES_CERTS_DIR}")
@@ -74,8 +77,14 @@ if(SG_BUILD_EXAMPLES)
     list(APPEND SG_EXAMPLES httpcomp)
   endif()
   if(SG_PATH_ROUTING)
-    list(APPEND SG_EXAMPLES entrypoint router_simple router_segments
-                router_vars router_srv)
+    list(
+      APPEND
+      SG_EXAMPLES
+      entrypoint
+      router_simple
+      router_segments
+      router_vars
+      router_srv)
   endif()
   set(SG_EXAMPLES
       ${SG_EXAMPLES}
@@ -86,7 +95,7 @@ if(SG_BUILD_EXAMPLES)
     option(SG_BUILD_${_EXAMPLE}_EXAMPLE "Build sg_${_example} example" ON)
     if(SG_BUILD_${_EXAMPLE}_EXAMPLE)
       list(APPEND SG_EXAMPLES_SOURCE
-                  ${SG_EXAMPLES_SOURCE_DIR}/example_${_example}.c)
+           ${SG_EXAMPLES_SOURCE_DIR}/example_${_example}.c)
       add_executable(example_${_example}
                      ${SG_EXAMPLES_SOURCE_DIR}/example_${_example}.c)
       target_link_libraries(example_${_example} ${_libs})

+ 77 - 0
examples/example_httpreq_isolate.c

@@ -0,0 +1,77 @@
+/*                         _
+ *   ___  __ _  __ _ _   _(_)
+ *  / __|/ _` |/ _` | | | | |
+ *  \__ \ (_| | (_| | |_| | |
+ *  |___/\__,_|\__, |\__,_|_|
+ *             |___/
+ *
+ * Cross-platform library which helps to develop web servers or frameworks.
+ *
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
+ *
+ * Sagui library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * Sagui library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sagui library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <sagui.h>
+
+/* NOTE: Error checking has been omitted to make it clear. */
+
+static void req_isolated_cb(__SG_UNUSED void *cls,
+                            __SG_UNUSED struct sg_httpreq *req,
+                            struct sg_httpres *res) {
+  sleep(3);
+  sg_httpres_send(
+    res,
+    "<html><head><title>Isolated request</title></head><body>Isolated and "
+    "delayed request</body></html>",
+    "text/html; charset=utf-8", 200);
+}
+
+static void req_cb(__SG_UNUSED void *cls, struct sg_httpreq *req,
+                   struct sg_httpres *res) {
+  if (strcmp(sg_httpreq_path(req), "/delayed") == 0)
+    sg_httpreq_isolate(req, req_isolated_cb, NULL);
+  else
+    sg_httpres_send(res,
+                    "<html><head><title>Hello world</title></head><body>Hello "
+                    "world</body></html>",
+                    "text/html; charset=utf-8", 200);
+}
+
+int main(int argc, const char *argv[]) {
+  struct sg_httpsrv *srv;
+  uint16_t port;
+  if (argc != 2) {
+    printf("%s <PORT>\n", argv[0]);
+    return EXIT_FAILURE;
+  }
+  port = strtol(argv[1], NULL, 10);
+  srv = sg_httpsrv_new(req_cb, NULL);
+  if (!sg_httpsrv_listen(srv, port, false)) {
+    sg_httpsrv_free(srv);
+    return EXIT_FAILURE;
+  }
+  fprintf(stdout, "Server running at http://localhost:%d\n",
+          sg_httpsrv_port(srv));
+  fflush(stdout);
+  getchar();
+  sg_httpsrv_free(srv);
+  return EXIT_SUCCESS;
+}

+ 12 - 15
examples/example_httpsrv_benchmark.c

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -28,13 +28,15 @@
 #include <stdint.h>
 #ifdef _WIN32
 #include <windows.h>
-#else
+#else /* _WIN32 */
 #include <unistd.h>
-#endif
+#endif /* _WIN32 */
 #include <sagui.h>
 
 /* NOTE: Error checking has been omitted to make it clear. */
 
+#define CONNECTION_LIMIT 1000 /* Change to 10000 for C10K problem. */
+
 static unsigned int get_cpu_count(void) {
 #ifdef _WIN32
 #ifndef _SC_NPROCESSORS_ONLN
@@ -42,27 +44,23 @@ static unsigned int get_cpu_count(void) {
   GetSystemInfo(&info);
 #define sysconf(void) info.dwNumberOfProcessors
 #define _SC_NPROCESSORS_ONLN
-#endif
-#endif
+#endif /* _SC_NPROCESSORS_ONLN */
+#endif /* _WIN32 */
 #ifdef _SC_NPROCESSORS_ONLN
   return (unsigned int) sysconf(_SC_NPROCESSORS_ONLN);
-#else
+#else /* _SC_NPROCESSORS_ONLN */
   return 0;
-#endif
+#endif /* _SC_NPROCESSORS_ONLN */
 }
 
 static void req_cb(__SG_UNUSED void *cls, __SG_UNUSED struct sg_httpreq *req,
                    struct sg_httpres *res) {
-  sg_httpres_send(res,
-                  "<html><head><title>Hello world</title></head><body>Hello "
-                  "world</body></html>",
-                  "text/html", 200);
+  sg_httpres_send(res, "Hello world", "text/plain", 200);
 }
 
 int main(int argc, const char *argv[]) {
   struct sg_httpsrv *srv;
   unsigned int cpu_count;
-  unsigned int con_limit;
   uint16_t port;
   if (argc != 2) {
     printf("%s <PORT>\n", argv[0]);
@@ -70,16 +68,15 @@ int main(int argc, const char *argv[]) {
   }
   port = strtol(argv[1], NULL, 10);
   cpu_count = get_cpu_count();
-  con_limit = 1000; /* Change to 10000 for C10K problem. */
   srv = sg_httpsrv_new(req_cb, NULL);
   sg_httpsrv_set_thr_pool_size(srv, cpu_count);
-  sg_httpsrv_set_con_limit(srv, con_limit);
+  sg_httpsrv_set_con_limit(srv, CONNECTION_LIMIT);
   if (!sg_httpsrv_listen(srv, port, false)) {
     sg_httpsrv_free(srv);
     return EXIT_FAILURE;
   }
   fprintf(stdout, "Number of processors: %d\n", cpu_count);
-  fprintf(stdout, "Connections limit: %d\n", con_limit);
+  fprintf(stdout, "Connections limit: %d\n", CONNECTION_LIMIT);
   fprintf(stdout, "Server running at http://localhost:%d\n",
           sg_httpsrv_port(srv));
   fflush(stdout);

+ 110 - 0
examples/example_httpsrv_sse.c

@@ -0,0 +1,110 @@
+/*                         _
+ *   ___  __ _  __ _ _   _(_)
+ *  / __|/ _` |/ _` | | | | |
+ *  \__ \ (_| | (_| | |_| | |
+ *  |___/\__,_|\__, |\__,_|_|
+ *             |___/
+ *
+ * Cross-platform library which helps to develop web servers or frameworks.
+ *
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
+ *
+ * Sagui library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * Sagui library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sagui library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#ifdef _WIN32
+#include <windows.h>
+#else /* _WIN32 */
+#include <unistd.h>
+#endif /* _WIN32 */
+#include <sagui.h>
+
+/* NOTE: Error checking has been omitted to make it clear. */
+
+#define PAGE                                                                   \
+  "<html>\n"                                                                   \
+  "<head>\n"                                                                   \
+  "<title>SSE example</title>\n"                                               \
+  "</head><body><h2 id=\"counter\">Please wait ...</h2>\n"                     \
+  "<script>\n"                                                                 \
+  "const es = new EventSource('/');\n"                                         \
+  "es.onmessage = function (ev) {\n"                                           \
+  "  document.getElementById('counter').innerText = 'Counting: ' + ev.data;\n" \
+  "};\n"                                                                       \
+  "</script>\n"                                                                \
+  "</body>\n"                                                                  \
+  "</html>"
+
+static unsigned int COUNT = 0;
+
+#ifdef _WIN32
+#define sleep Sleep
+#endif /* _WIN32 */
+
+static ssize_t sse_read_cb(__SG_UNUSED void *handle, uint64_t offset, char *buf,
+                           size_t size) {
+  char msg[20];
+  if (offset == 0) {
+    size = sprintf(msg, "retry: 1000\n");
+  } else {
+    size = sprintf(msg, "data: %d\n\n", ++COUNT);
+    sleep(1);
+  }
+  memcpy(buf, msg, size);
+  msg[size] = '\0';
+  return size;
+}
+
+static void req_cb(__SG_UNUSED void *cls, struct sg_httpreq *req,
+                   struct sg_httpres *res) {
+  struct sg_strmap *pair;
+  struct sg_strmap **req_headers = sg_httpreq_headers(req);
+  struct sg_strmap **res_headers = sg_httpres_headers(res);
+  if (sg_strmap_find(*req_headers, "Accept", &pair) == 0 &&
+      strstr(sg_strmap_val(pair), "text/event-stream")) {
+    sg_strmap_set(res_headers, "Access-Control-Allow-Origin", "*");
+    sg_strmap_set(res_headers, "Content-Type", "text/event-stream");
+    sg_httpres_sendstream(res, 0, sse_read_cb, NULL, NULL, 200);
+    return;
+  }
+  if (strcmp(sg_httpreq_path(req), "/favicon.ico") == 0) {
+    sg_httpres_send(res, "", "", 204);
+    return;
+  }
+  sg_httpres_send(res, PAGE, "text/html; charset=utf-8", 200);
+}
+
+int main(int argc, const char *argv[]) {
+  struct sg_httpsrv *srv;
+  if (argc != 2) {
+    printf("%s <PORT>\n", argv[0]);
+    return EXIT_FAILURE;
+  }
+  srv = sg_httpsrv_new(req_cb, NULL);
+  if (!sg_httpsrv_listen(srv, strtol(argv[1], NULL, 10), true)) {
+    sg_httpsrv_free(srv);
+    return EXIT_FAILURE;
+  }
+  fprintf(stdout, "Server running at http://localhost:%d\n",
+          sg_httpsrv_port(srv));
+  fflush(stdout);
+  getchar();
+  sg_httpsrv_free(srv);
+  return EXIT_SUCCESS;
+}

+ 6 - 5
examples/example_httpuplds.c

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -34,9 +34,9 @@
 
 #ifdef _WIN32
 #define PATH_SEP '\\'
-#else
+#else /* _WIN32 */
 #define PATH_SEP '/'
-#endif
+#endif /* _WIN32 */
 
 #define PAGE_FORM                                                              \
   "<html>"                                                                     \
@@ -105,10 +105,11 @@ static void req_cb(__SG_UNUSED void *cls, struct sg_httpreq *req,
     process_uploads(req, res);
   else {
     qs = sg_httpreq_params(req);
-    if (qs) {
+    if (qs && sg_strmap_count(*qs) > 0) {
       file = sg_strmap_get(*qs, "file");
       if (file) {
-        sprintf(path, "%s%c%s", sg_tmpdir(), PATH_SEP, file);
+        sprintf(path, "%s%c%s", sg_httpsrv_upld_dir(sg_httpreq_srv(req)),
+                PATH_SEP, file);
         sg_httpres_download(res, path);
       }
     } else

+ 59 - 22
include/sagui.h

@@ -52,29 +52,29 @@ extern "C" {
 #ifdef _WIN32
 #ifdef BUILDING_LIBSAGUI
 #define SG_EXTERN __declspec(dllexport) extern
-#else
+#else /* _WIN32 */
 #define SG_EXTERN __declspec(dllimport) extern
-#endif
-#else
+#endif /* _WIN32 */
+#else /* BUILDING_LIBSAGUI */
 #define SG_EXTERN extern
-#endif
-#endif
+#endif /* BUILDING_LIBSAGUI */
+#endif /* SG_EXTERN */
 
 #ifndef __SG_UNUSED
 #define __SG_UNUSED __attribute__((unused))
-#endif
+#endif /* __SG_UNUSED */
 
 #ifndef __SG_MALLOC
 #define __SG_MALLOC __attribute__((malloc))
-#endif
+#endif /* __SG_MALLOC */
 
 #ifndef __SG_FORMAT
 #define __SG_FORMAT(...) __attribute__((format(printf, __VA_ARGS__)))
-#endif
+#endif /* __SG_FORMAT */
 
-#define SG_VERSION_MAJOR 2
-#define SG_VERSION_MINOR 5
-#define SG_VERSION_PATCH 5
+#define SG_VERSION_MAJOR 3
+#define SG_VERSION_MINOR 0
+#define SG_VERSION_PATCH 0
 #define SG_VERSION_HEX                                                         \
   ((SG_VERSION_MAJOR << 16) | (SG_VERSION_MINOR << 8) | (SG_VERSION_PATCH))
 
@@ -579,7 +579,7 @@ struct sg_httpres;
 struct sg_httpsrv;
 
 /**
- * Callback signature used to handle client events.
+ * Callback signature used to handle client connection events.
  * \param[out] cls User-defined closure.
  * \param[out] client Socket handle of the client.
  * \param[in,out] closed Indicates if the client is connected allowing to
@@ -684,7 +684,8 @@ SG_EXTERN int sg_httpauth_deny(struct sg_httpauth *auth, const char *reason,
                                const char *content_type);
 
 /**
- * Cancels the authentication loop while the user is trying to acess the server.
+ * Cancels the authentication loop while the user is trying to access
+ * the server.
  * \param[in] auth Authentication handle.
  * \retval 0 Success.
  * \retval EINVAL Invalid argument.
@@ -816,6 +817,14 @@ SG_EXTERN int sg_httpupld_save(struct sg_httpupld *upld, bool overwritten);
 SG_EXTERN int sg_httpupld_save_as(struct sg_httpupld *upld, const char *path,
                                   bool overwritten);
 
+/**
+ * Returns the server instance.
+ * \param[in] req Request handle.
+ * \return Reference to the server instance.
+ * \retval NULL If \pr{req} is null and sets the `errno` to `EINVAL`
+ */
+SG_EXTERN struct sg_httpsrv *sg_httpreq_srv(struct sg_httpreq *req);
+
 /**
  * Returns the client headers into #sg_strmap map.
  * \param[in] req Request handle.
@@ -920,7 +929,25 @@ SG_EXTERN const void *sg_httpreq_client(struct sg_httpreq *req);
  */
 SG_EXTERN void *sg_httpreq_tls_session(struct sg_httpreq *req);
 
-#endif
+#endif /* SG_HTTPS_SUPPORT */
+
+/**
+ * Isolates a request from the main event loop to an own dedicated thread,
+ * bringing it back when the request finishes.
+ * \param[in] req Request handle.
+ * \param[in] cb Callback to handle requests and responses isolated from the
+ * main event loop.
+ * \param[in] cls User-defined closure.
+ * \retval 0 Success.
+ * \retval EINVAL Invalid argument.
+ * \retval ENOMEM Out of memory.
+ * \retval E<ERROR> Any returned error from the OS threading library.
+ * \note Isolated requestes will not time out.
+ * \note While a request is isolated, the library will not detect disconnects
+ * by the client.
+ */
+SG_EXTERN int sg_httpreq_isolate(struct sg_httpreq *req, sg_httpreq_cb cb,
+                                 void *cls);
 
 /**
  * Sets user data to the request handle.
@@ -1293,7 +1320,7 @@ SG_EXTERN int sg_httpres_zsendfile(struct sg_httpres *res, uint64_t size,
                                    const char *filename, bool downloaded,
                                    unsigned int status);
 
-#endif
+#endif /* SG_HTTP_COMPRESSION */
 
 /**
  * Clears all headers, cookies, statuses and internal buffers of the response
@@ -1315,6 +1342,7 @@ SG_EXTERN int sg_httpres_clear(struct sg_httpres *res);
  * \retval NULL If no memory space is available.
  * \retval NULL If the \pr{req_cb} or \pr{err_cb} is null and sets the `errno`
  * to `EINVAL`.
+ * \retval NULL If a threading operation fails and sets its error to `errno`.
  */
 SG_EXTERN struct sg_httpsrv *sg_httpsrv_new2(sg_httpauth_cb auth_cb,
                                              sg_httpreq_cb req_cb,
@@ -1327,6 +1355,7 @@ SG_EXTERN struct sg_httpsrv *sg_httpsrv_new2(sg_httpauth_cb auth_cb,
  * \param[in] cls User-defined closure.
  * \return New HTTP server handle.
  * \retval NULL If the \pr{cb} is null and sets the `errno` to `EINVAL`.
+ * \retval NULL If a threading operation fails and sets its error to `errno`.
  */
 SG_EXTERN struct sg_httpsrv *sg_httpsrv_new(sg_httpreq_cb cb,
                                             void *cls) __SG_MALLOC;
@@ -1385,7 +1414,7 @@ SG_EXTERN bool sg_httpsrv_tls_listen(struct sg_httpsrv *srv, const char *key,
                                      const char *cert, uint16_t port,
                                      bool threaded);
 
-#endif
+#endif /* SG_HTTPS_SUPPORT */
 
 /**
  * Starts the HTTP server.
@@ -1404,10 +1433,9 @@ SG_EXTERN bool sg_httpsrv_listen(struct sg_httpsrv *srv, uint16_t port,
 /**
  * Stops the server not to accept new connections.
  * \param[in] srv Server handle.
- * \retval 0 If the server is stopped. If \pr{srv} is null, sets the `errno` to
- * `EINVAL`.
- * \note When #sg_httpsrv_set_con_timeout() is set, the server waits for the
- * clients to be closed before shutting down.
+ * \retval 0 If the server is stopped.
+ * \retval EINVAL Invalid argument.
+ * \retval EALREADY Already shut down.
  */
 SG_EXTERN int sg_httpsrv_shutdown(struct sg_httpsrv *srv);
 
@@ -1428,7 +1456,7 @@ SG_EXTERN uint16_t sg_httpsrv_port(struct sg_httpsrv *srv);
 SG_EXTERN bool sg_httpsrv_is_threaded(struct sg_httpsrv *srv);
 
 /**
- * Sets the server callback for client events.
+ * Sets the server callback for client connection events.
  * \param[in] srv Server handle.
  * \param[in] cb Callback to handle client events.
  * \param[in] cls User-defined closure.
@@ -1580,6 +1608,15 @@ SG_EXTERN int sg_httpsrv_set_con_limit(struct sg_httpsrv *srv,
  */
 SG_EXTERN unsigned int sg_httpsrv_con_limit(struct sg_httpsrv *srv);
 
+/**
+ * Returns the MHD instance.
+ * \param[in] srv Server handle.
+ * \return MHD instance.
+ * \return NULL If the server is shut down.
+ * \retval NULL If \pr{srv} is null and sets the `errno` to `EINVAL`.
+ */
+SG_EXTERN void *sg_httpsrv_handle(struct sg_httpsrv *srv);
+
 /** \} */
 
 #ifdef SG_PATH_ROUTING
@@ -2019,7 +2056,7 @@ SG_EXTERN int sg_router_dispatch(struct sg_router *router, const char *path,
 
 /** \} */
 
-#endif
+#endif /* SG_PATH_ROUTING */
 
 #ifdef __cplusplus
 }

+ 15 - 14
src/CMakeLists.txt

@@ -22,7 +22,7 @@
 #
 # Cross-platform library which helps to develop web servers or frameworks.
 #
-# Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+# Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
 #
 # Sagui library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -54,30 +54,31 @@ set(SG_SOURCE_DIR
     PARENT_SCOPE)
 
 list(
-  APPEND SG_C_SOURCE
-         ${SG_SOURCE_DIR}/sg_utils.c
-         ${SG_SOURCE_DIR}/sg_extra.c
-         ${SG_SOURCE_DIR}/sg_str.c
-         ${SG_SOURCE_DIR}/sg_strmap.c
-         ${SG_SOURCE_DIR}/sg_httpauth.c
-         ${SG_SOURCE_DIR}/sg_httpuplds.c
-         ${SG_SOURCE_DIR}/sg_httpreq.c
-         ${SG_SOURCE_DIR}/sg_httpres.c
-         ${SG_SOURCE_DIR}/sg_httpsrv.c)
+  APPEND
+  SG_C_SOURCE
+  ${SG_SOURCE_DIR}/sg_utils.c
+  ${SG_SOURCE_DIR}/sg_extra.c
+  ${SG_SOURCE_DIR}/sg_str.c
+  ${SG_SOURCE_DIR}/sg_strmap.c
+  ${SG_SOURCE_DIR}/sg_httpauth.c
+  ${SG_SOURCE_DIR}/sg_httpuplds.c
+  ${SG_SOURCE_DIR}/sg_httpreq.c
+  ${SG_SOURCE_DIR}/sg_httpres.c
+  ${SG_SOURCE_DIR}/sg_httpsrv.c)
 if(NOT HAVE_INET_NTOP)
   list(APPEND SG_C_SOURCE ${SG_SOURCE_DIR}/inet.c)
 endif()
 if(SG_PATH_ROUTING)
   list(APPEND SG_C_SOURCE ${SG_SOURCE_DIR}/sg_entrypoint.c
-              ${SG_SOURCE_DIR}/sg_entrypoints.c ${SG_SOURCE_DIR}/sg_routes.c
-              ${SG_SOURCE_DIR}/sg_router.c)
+       ${SG_SOURCE_DIR}/sg_entrypoints.c ${SG_SOURCE_DIR}/sg_routes.c
+       ${SG_SOURCE_DIR}/sg_router.c)
 endif()
 set(SG_C_SOURCE
     ${SG_C_SOURCE}
     PARENT_SCOPE)
 
 list(APPEND SG_SOURCE ${SG_INCLUDE_DIR}/sagui.h ${SG_SOURCE_DIR}/sg_macros.h
-            ${SG_C_SOURCE})
+     ${SG_C_SOURCE})
 if(WIN32 AND BUILD_SHARED_LIBS)
   list(APPEND SG_SOURCE ${SG_LIBSAGUI_RC})
 endif()

+ 3 - 3
src/inet.h

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -29,9 +29,9 @@
 
 #ifdef _WIN32
 #include <ws2tcpip.h>
-#else
+#else /* _WIN32 */
 #include <unistd.h>
-#endif
+#endif /* _WIN32 */
 
 const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
 

+ 4 - 4
src/sg_extra.c

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -30,7 +30,7 @@
 #include "sg_macros.h"
 #ifdef SG_HTTP_COMPRESSION
 #include "zlib.h"
-#endif
+#endif /* SG_HTTP_COMPRESSION */
 #include "microhttpd.h"
 #include "sg_extra.h"
 #include "sg_strmap.h"
@@ -51,7 +51,7 @@ ssize_t sg_eor(bool err) {
   return
 #ifdef __ANDROID__
     (ssize_t)
-#endif
+#endif /* __ANDROID__ */
         err ?
       MHD_CONTENT_READER_END_WITH_ERROR :
       MHD_CONTENT_READER_END_OF_STREAM;
@@ -129,4 +129,4 @@ int sg__zdeflate(z_stream *stream, Bytef *zbuf, int flush, z_const Bytef *src,
   return 0;
 }
 
-#endif
+#endif /* SG_HTTP_COMPRESSION */

+ 3 - 3
src/sg_extra.h

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -30,7 +30,7 @@
 #include "sg_macros.h"
 #ifdef SG_HTTP_COMPRESSION
 #include "zlib.h"
-#endif
+#endif /* SG_HTTP_COMPRESSION */
 #include "microhttpd.h"
 #include "sg_strmap.h"
 #include "sagui.h"
@@ -54,6 +54,6 @@ SG__EXTERN int sg__zdeflate(z_stream *stream, Bytef *zbuf, int flush,
                             z_const Bytef *src, uInt src_size, Bytef **dest,
                             z_size_t *dest_size);
 
-#endif
+#endif /* SG_HTTP_COMPRESSION */
 
 #endif /* SG_EXTRA_H */

+ 59 - 4
src/sg_httpreq.c

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -25,15 +25,29 @@
  */
 
 #include <errno.h>
+#include <pthread.h>
 #include "sg_macros.h"
 #include "microhttpd.h"
 #include "sagui.h"
 #include "sg_extra.h"
-#include "sg_httpres.h"
 #include "sg_httpreq.h"
+#include "sg_httpres.h"
 #include "sg_httpauth.h"
+#include "sg_httpsrv.h"
+
+static void *sg__httpreq_isolate_cb(void *cls) {
+  struct sg__httpreq_isolated *isolated = cls;
+  isolated->cb(isolated->cls, isolated->handle, isolated->handle->res);
+  sg__httpsrv_lock(isolated->handle->srv);
+  LL_DELETE(isolated->handle->srv->isolated_list, isolated);
+  sg__httpsrv_unlock(isolated->handle->srv);
+  MHD_resume_connection(isolated->handle->con);
+  sg_free(isolated);
+  return NULL;
+}
 
-struct sg_httpreq *sg__httpreq_new(struct MHD_Connection *con,
+struct sg_httpreq *sg__httpreq_new(struct sg_httpsrv *srv,
+                                   struct MHD_Connection *con,
                                    const char *version, const char *method,
                                    const char *path) {
   struct sg_httpreq *req = sg_alloc(sizeof(struct sg_httpreq));
@@ -48,6 +62,7 @@ struct sg_httpreq *sg__httpreq_new(struct MHD_Connection *con,
   req->payload = sg_str_new();
   if (!req->payload)
     goto error;
+  req->srv = srv;
   req->con = con;
   req->version = version;
   req->method = method;
@@ -75,6 +90,13 @@ void sg__httpreq_free(struct sg_httpreq *req) {
   sg_free(req);
 }
 
+struct sg_httpsrv *sg_httpreq_srv(struct sg_httpreq *req) {
+  if (req)
+    return req->srv;
+  errno = EINVAL;
+  return NULL;
+}
+
 struct sg_strmap **sg_httpreq_headers(struct sg_httpreq *req) {
   if (!req) {
     errno = EINVAL;
@@ -181,7 +203,40 @@ void *sg_httpreq_tls_session(struct sg_httpreq *req) {
   return NULL;
 }
 
-#endif
+#endif /* SG_HTTPS_SUPPORT */
+
+int sg_httpreq_isolate(struct sg_httpreq *req, sg_httpreq_cb cb, void *cls) {
+  struct sg__httpreq_isolated *isolated;
+  int errnum = 0;
+  if (!req || !cb)
+    return EINVAL;
+  sg__httpsrv_lock(req->srv);
+  if (req->isolated) {
+    errnum = EALREADY;
+    goto error;
+  }
+  isolated = sg_malloc(sizeof(struct sg__httpreq_isolated));
+  if (!isolated) {
+    errnum = ENOMEM;
+    goto error;
+  }
+  isolated->handle = req;
+  isolated->cb = cb;
+  isolated->cls = cls;
+  MHD_suspend_connection(req->con);
+  LL_APPEND(req->srv->isolated_list, isolated);
+  req->isolated = true;
+  errnum =
+    pthread_create(&isolated->thread, NULL, sg__httpreq_isolate_cb, isolated);
+  if (errnum != 0) {
+    LL_DELETE(req->srv->isolated_list, isolated);
+    sg_free(isolated);
+    MHD_resume_connection(req->con);
+  }
+error:
+  sg__httpsrv_unlock(req->srv);
+  return errnum;
+}
 
 int sg_httpreq_set_user_data(struct sg_httpreq *req, void *data) {
   if (!req)

+ 16 - 5
src/sg_httpreq.h

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -28,13 +28,16 @@
 #define SG_HTTPREQ_H
 
 #include <stdbool.h>
+#include <pthread.h>
 #include "sg_macros.h"
 #include "microhttpd.h"
 #include "sagui.h"
 #include "sg_httpuplds.h"
 #include "sg_httpres.h"
+#include "sg_httpsrv.h"
 
 struct sg_httpreq {
+  struct sg_httpsrv *srv;
   struct MHD_Connection *con;
   struct MHD_PostProcessor *pp;
   struct sg_httpauth *auth;
@@ -54,12 +57,20 @@ struct sg_httpreq {
   uint64_t total_uplds_size;
   size_t total_fields_size;
   bool is_uploading;
+  bool isolated;
 };
 
-SG__EXTERN struct sg_httpreq *sg__httpreq_new(struct MHD_Connection *con,
-                                              const char *version,
-                                              const char *method,
-                                              const char *path);
+struct sg__httpreq_isolated {
+  pthread_t thread;
+  struct sg_httpreq *handle;
+  sg_httpreq_cb cb;
+  void *cls;
+  struct sg__httpreq_isolated *next;
+};
+
+SG__EXTERN struct sg_httpreq *
+  sg__httpreq_new(struct sg_httpsrv *srv, struct MHD_Connection *con,
+                  const char *version, const char *method, const char *path);
 
 SG__EXTERN void sg__httpreq_free(struct sg_httpreq *req);
 

+ 24 - 12
src/sg_httpres.c

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -36,7 +36,7 @@
 #include "sg_macros.h"
 #ifdef SG_HTTP_COMPRESSION
 #include "zlib.h"
-#endif
+#endif /* SG_HTTP_COMPRESSION */
 #include "microhttpd.h"
 #include "sagui.h"
 #include "sg_utils.h"
@@ -50,15 +50,27 @@ static void sg__httpres_openfile(struct sg_httpres *res, const char *filename,
   const char *fn;
   size_t fn_size;
   char *disp;
+#ifdef _WIN32
+  if (stat(filename, sbuf)) {
+    *errnum = errno;
+    return;
+  }
+  if (S_ISDIR(sbuf->st_mode)) {
+    *errnum = EISDIR;
+    return;
+  }
+#endif /* _WIN32 */
   *fd = open(filename, O_RDONLY);
   if ((*fd == -1) || fstat(*fd, sbuf)) {
     *errnum = errno;
     return;
   }
+#ifndef _WIN32
   if (S_ISDIR(sbuf->st_mode)) {
     *errnum = EISDIR;
     return;
   }
+#endif /* _WIN32 */
   if (!S_ISREG(sbuf->st_mode)) {
     *errnum = EBADF;
     return;
@@ -69,7 +81,7 @@ static void sg__httpres_openfile(struct sg_httpres *res, const char *filename,
   }
   if (disposition) {
 #define SG__FNFMT "%s; filename=\"%s\""
-    fn = basename(filename);
+    fn = sg__basename(filename);
     fn_size = (size_t) snprintf(NULL, 0, SG__FNFMT, disposition, fn) + 1;
     disp = sg_malloc(fn_size);
     if (!disp) {
@@ -77,7 +89,7 @@ static void sg__httpres_openfile(struct sg_httpres *res, const char *filename,
       return;
     }
     snprintf(disp, fn_size, SG__FNFMT, disposition, fn);
-#undef SG__FNFMT
+#undef SG__FNFMT /* SG__FNFMT */
     *errnum =
       sg_strmap_set(&res->headers, MHD_HTTP_HEADER_CONTENT_DISPOSITION, disp);
     sg_free(disp);
@@ -171,9 +183,9 @@ static ssize_t sg__httpres_gzread_cb(void *handle, __SG_UNUSED uint64_t offset,
     mem[8] = (char) 0x00;
 #ifdef _WIN32
     mem[9] = (char) 0x0b;
-#else
+#else /* _WIN32 */
     mem[9] = (char) 0x03;
-#endif
+#endif /* _WIN32 */
     holder->crc = crc32(0L, Z_NULL, 0);
     return 10;
   }
@@ -251,7 +263,7 @@ static void sg__httpres_gzfree_cb(void *handle) {
   sg_free(holder);
 }
 
-#endif
+#endif /* SG_HTTP_COMPRESSION */
 
 struct sg_httpres *sg__httpres_new(struct MHD_Connection *con) {
   struct sg_httpres *res = sg_alloc(sizeof(struct sg_httpres));
@@ -489,9 +501,9 @@ int sg_httpres_zsendstream2(struct sg_httpres *res, int level, uint64_t size,
   res->status = status;
 #ifdef SG_TESTING
   errnum = 0;
-#else
+#else /* SG_TESTING */
   return 0;
-#endif
+#endif /* SG_TESTING */
 error_res:
   sg_free(holder->buf_in);
 error_buf_in:
@@ -569,9 +581,9 @@ int sg_httpres_zsendfile2(struct sg_httpres *res, int level, uint64_t size,
   res->status = status;
 #ifdef SG_TESTING
   errnum = 0;
-#else
+#else /* SG_TESTING */
   return 0;
-#endif
+#endif /* SG_TESTING */
 error_res:
   sg_free(holder->handle);
 error_handle:
@@ -595,7 +607,7 @@ int sg_httpres_zsendfile(struct sg_httpres *res, uint64_t size,
                                status);
 }
 
-#endif
+#endif /* SG_HTTP_COMPRESSION */
 
 int sg_httpres_clear(struct sg_httpres *res) {
   if (!res)

+ 3 - 3
src/sg_httpres.h

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -31,7 +31,7 @@
 #ifdef SG_HTTP_COMPRESSION
 #include <stdint.h>
 #include "zlib.h"
-#endif
+#endif /* SG_HTTP_COMPRESSION */
 #include "microhttpd.h"
 #include "sagui.h"
 
@@ -86,7 +86,7 @@ struct sg__httpres_gzholder {
   enum sg__httpres_gzstatus status;
 };
 
-#endif
+#endif /* SG_HTTP_COMPRESSION */
 
 SG__EXTERN struct sg_httpres *sg__httpres_new(struct MHD_Connection *con);
 

+ 88 - 15
src/sg_httpsrv.c

@@ -29,13 +29,16 @@
 #include <stdio.h>
 #include <string.h>
 #include <errno.h>
+#include <pthread.h>
 #include "sg_macros.h"
+#include "utlist.h"
 #include "microhttpd.h"
 #include "sagui.h"
 #include "sg_utils.h"
-#include "sg_httpsrv.h"
 #include "sg_httpauth.h"
 #include "sg_httpreq.h"
+#include "sg_httpreq.h"
+#include "sg_httpsrv.h"
 
 static void sg__httpsrv_oel(void *cls, const char *fmt, va_list ap) {
   struct sg_httpsrv *srv = cls;
@@ -56,16 +59,16 @@ static int sg__httpsrv_ahc(void *cls, struct MHD_Connection *con,
   const union MHD_ConnectionInfo *info;
 #ifdef SG_TESTING
   if (con) {
-#endif
+#endif /* SG_TESTING */
     info =
       MHD_get_connection_info(con, MHD_CONNECTION_INFO_SOCKET_CONTEXT, NULL);
     if (info && info->socket_context)
       return MHD_NO;
 #ifdef SG_TESTING
   }
-#endif
+#endif /* SG_TESTING */
   if (!req) {
-    req = sg__httpreq_new(con, version, method, url);
+    req = sg__httpreq_new(srv, con, version, method, url);
     if (!req)
       return MHD_NO;
     *con_cls = req;
@@ -80,9 +83,19 @@ static int sg__httpsrv_ahc(void *cls, struct MHD_Connection *con,
     if (sg__httpuplds_process(srv, req, con, upld_data, upld_data_size,
                               &req->res->ret))
       return req->res->ret;
-    srv->req_cb(srv->cls, req, req->res);
+    if (!req->isolated)
+      srv->req_cb(srv->cls, req, req->res);
   }
-  return sg__httpres_dispatch(req->res);
+#ifdef SG_TESTING
+  if (con) {
+#endif /* SG_TESTING */
+    info = MHD_get_connection_info(
+      con, MHD_CONNECTION_INFO_CONNECTION_SUSPENDED, NULL);
+#ifdef SG_TESTING
+  } else
+    info = NULL;
+#endif /* SG_TESTING */
+  return info && info->suspended ? MHD_YES : sg__httpres_dispatch(req->res);
 }
 
 static void sg__httpsrv_rcc(void *cls, __SG_UNUSED struct MHD_Connection *con,
@@ -140,6 +153,7 @@ static bool sg__httpsrv_listen(struct sg_httpsrv *srv, const char *key,
     return false;
   }
   flags = MHD_USE_DUAL_STACK | MHD_USE_ITC | MHD_USE_ERROR_LOG |
+          MHD_ALLOW_SUSPEND_RESUME |
           (threaded ?
              MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_THREAD_PER_CONNECTION :
              MHD_USE_AUTO_INTERNAL_THREAD);
@@ -187,9 +201,26 @@ void sg__httpsrv_eprintf(struct sg_httpsrv *srv, const char *fmt, ...) {
   srv->err_cb(srv->cls, err);
 }
 
+void sg__httpsrv_lock(struct sg_httpsrv *srv) {
+  char err[SG_ERR_SIZE];
+  int errnum = pthread_mutex_lock(&srv->mutex);
+  if (errnum != 0)
+    sg__httpsrv_eprintf(srv, _("Failed to lock mutex: %s.\n"),
+                        sg_strerror(errnum, err, sizeof(err)));
+}
+
+void sg__httpsrv_unlock(struct sg_httpsrv *srv) {
+  char err[SG_ERR_SIZE];
+  int errnum = pthread_mutex_unlock(&srv->mutex);
+  if (errnum != 0)
+    sg__httpsrv_eprintf(srv, _("Failed to unlock mutex: %s.\n"),
+                        sg_strerror(errnum, err, sizeof(err)));
+}
+
 struct sg_httpsrv *sg_httpsrv_new2(sg_httpauth_cb auth_cb, sg_httpreq_cb req_cb,
                                    sg_err_cb err_cb, void *cls) {
   struct sg_httpsrv *srv;
+  int errnum;
   if (!req_cb || !err_cb) {
     errno = EINVAL;
     return NULL;
@@ -197,6 +228,18 @@ struct sg_httpsrv *sg_httpsrv_new2(sg_httpauth_cb auth_cb, sg_httpreq_cb req_cb,
   srv = sg_alloc(sizeof(struct sg_httpsrv));
   if (!srv)
     return NULL;
+  srv->uplds_dir = sg_tmpdir();
+  if (!srv->uplds_dir) {
+    sg_free(srv);
+    return NULL;
+  }
+  errnum = pthread_mutex_init(&srv->mutex, NULL);
+  if (errnum != 0) {
+    sg_free(srv->uplds_dir);
+    sg_free(srv);
+    errno = errnum;
+    return NULL;
+  }
   srv->auth_cb = auth_cb;
   srv->req_cb = req_cb;
   srv->err_cb = err_cb;
@@ -207,20 +250,15 @@ struct sg_httpsrv *sg_httpsrv_new2(sg_httpauth_cb auth_cb, sg_httpreq_cb req_cb,
   srv->upld_free_cb = sg__httpupld_free_cb;
   srv->upld_save_cb = sg__httpupld_save_cb;
   srv->upld_save_as_cb = sg__httpupld_save_as_cb;
-  srv->uplds_dir = sg_tmpdir();
-  if (!srv->uplds_dir) {
-    sg_free(srv);
-    return NULL;
-  }
 #ifdef __arm__
   srv->post_buf_size = 1024; /* ~1 Kb */
   srv->payld_limit = 1048576; /* ~1 MB */
   srv->uplds_limit = 16777216; /* ~16 MB */
-#else
+#else /* __arm__ */
   srv->post_buf_size = 4096; /* ~4 kB */
   srv->payld_limit = 4194304; /* ~4 MB */
   srv->uplds_limit = 67108864; /* ~64 MB */
-#endif
+#endif /* __arm__ */
   return srv;
 }
 
@@ -229,10 +267,36 @@ struct sg_httpsrv *sg_httpsrv_new(sg_httpreq_cb cb, void *cls) {
 }
 
 void sg_httpsrv_free(struct sg_httpsrv *srv) {
+  const union MHD_ConnectionInfo *info;
+  struct sg__httpreq_isolated *isolated, *tmp;
+  char err[SG_ERR_SIZE];
+  int errnum;
   if (!srv)
     return;
-  sg_free(srv->uplds_dir);
+  sg__httpsrv_lock(srv);
+  LL_FOREACH_SAFE(srv->isolated_list, isolated, tmp) {
+    sg__httpsrv_unlock(srv);
+    errnum = pthread_join(isolated->thread, NULL);
+    if (errnum != 0)
+      sg__httpsrv_eprintf(srv, _("Failed to join thread %p: %s.\n"),
+                          (void *) &isolated->thread,
+                          sg_strerror(errnum, err, sizeof(err)));
+    sg__httpsrv_lock(srv);
+  }
+  sg__httpsrv_unlock(srv);
+  sg__httpsrv_lock(srv);
+  LL_FOREACH_SAFE(srv->isolated_list, isolated, tmp) {
+    info = MHD_get_connection_info(
+      isolated->handle->con, MHD_CONNECTION_INFO_CONNECTION_SUSPENDED, NULL);
+    if (info && info->suspended)
+      MHD_resume_connection(isolated->handle->con);
+    LL_DELETE(srv->isolated_list, isolated);
+    sg_free(isolated);
+  }
+  sg__httpsrv_unlock(srv);
   sg_httpsrv_shutdown(srv);
+  sg_free(srv->uplds_dir);
+  pthread_mutex_destroy(&srv->mutex);
   sg_free(srv);
 }
 
@@ -257,7 +321,7 @@ bool sg_httpsrv_tls_listen(struct sg_httpsrv *srv, const char *key,
   return false;
 }
 
-#endif
+#endif /* SG_HTTPS_SUPPORT */
 
 bool sg_httpsrv_listen(struct sg_httpsrv *srv, uint16_t port, bool threaded) {
   return sg__httpsrv_listen(srv, NULL, NULL, NULL, NULL, NULL, port, threaded);
@@ -266,6 +330,8 @@ bool sg_httpsrv_listen(struct sg_httpsrv *srv, uint16_t port, bool threaded) {
 int sg_httpsrv_shutdown(struct sg_httpsrv *srv) {
   if (!srv)
     return EINVAL;
+  if (!srv->handle)
+    return EALREADY;
   if (srv->handle) {
     MHD_stop_daemon(srv->handle);
     srv->handle = NULL;
@@ -412,3 +478,10 @@ unsigned int sg_httpsrv_con_limit(struct sg_httpsrv *srv) {
   errno = EINVAL;
   return 0;
 }
+
+void *sg_httpsrv_handle(struct sg_httpsrv *srv) {
+  if (srv)
+    return srv->handle;
+  errno = EINVAL;
+  return 0;
+}

+ 8 - 0
src/sg_httpsrv.h

@@ -28,12 +28,16 @@
 #define SG_HTTPSRV_H
 
 #include <stdint.h>
+#include <pthread.h>
 #include "sg_macros.h"
 #include "microhttpd.h"
 #include "sagui.h"
+#include "sg_httpreq.h"
 
 struct sg_httpsrv {
   struct MHD_Daemon *handle;
+  struct sg__httpreq_isolated *isolated_list;
+  pthread_mutex_t mutex;
   sg_httpsrv_cli_cb cli_cb;
   sg_httpauth_cb auth_cb;
   sg_httpupld_cb upld_cb;
@@ -58,4 +62,8 @@ struct sg_httpsrv {
 SG__EXTERN void sg__httpsrv_eprintf(struct sg_httpsrv *srv, const char *fmt,
                                     ...);
 
+SG__EXTERN void sg__httpsrv_lock(struct sg_httpsrv *srv);
+
+SG__EXTERN void sg__httpsrv_unlock(struct sg_httpsrv *srv);
+
 #endif /* SG_HTTPSRV_H */

+ 38 - 38
src/sg_macros.h

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -33,23 +33,39 @@
 #ifndef NDEBUG
 #include <unistd.h>
 #include <errno.h>
-#endif
+#endif /* NDEBUG */
 #include "sagui.h"
 
+/* used by utstring library */
+#ifndef utstring_oom
+#define utstring_oom() (void) 0
+#endif /* utstring_oom */
+
+/* used by uthash library */
+#ifndef HASH_NONFATAL_OOM
+#define HASH_NONFATAL_OOM 1
+#endif /* HASH_NONFATAL_OOM */
+#ifndef uthash_malloc
+#define uthash_malloc sg_malloc
+#endif /* uthash_malloc */
+#ifndef uthash_free
+#define uthash_free(p, sz) sg_free((p))
+#endif /* uthash_free */
+
+/* used bt pcre2 library */
 #ifdef SG_PATH_ROUTING
 #ifndef PCRE2_CODE_UNIT_WIDTH
 #define PCRE2_CODE_UNIT_WIDTH 8
-#endif
-#include "pcre2.h"
-#endif
+#endif /* PCRE2_CODE_UNIT_WIDTH */
+#endif /* SG_PATH_ROUTING */
 
 #ifndef SG__EXTERN
 #if defined(_WIN32) && defined(BUILD_TESTING)
 #define SG__EXTERN __declspec(dllexport) extern
-#else
+#else /* SG__EXTERN */
 #define SG__EXTERN
-#endif
-#endif
+#endif /* _WIN32 && BUILD_TESTING */
+#endif /* SG__EXTERN */
 
 /* macro to make it easy to mark text for translation */
 #define _(String) (String)
@@ -60,56 +76,40 @@
 
 #ifdef _WIN32
 #define PATH_SEP '\\'
-#else
+#else /* _WIN32 */
 #define PATH_SEP '/'
-#endif
+#endif /* _WIN32 */
 
 #ifndef SG__BLOCK_SIZE
 #ifdef _WIN32
 #define SG__BLOCK_SIZE 16384 /* 16k */
-#else
+#else /* _WIN32 */
 #define SG__BLOCK_SIZE 4096 /* 4k */
-#endif
-#endif
+#endif /* _WIN32 */
+#endif /* SG__BLOCK_SIZE */
 
 #ifndef sg__off_t
 #ifdef _WIN64
 #define sg__off_t off64_t
-#else
+#else /* _WIN64 */
 #define sg__off_t off_t
-#endif
-#endif
+#endif /* _WIN64 */
+#endif /* sg__off_t */
 
 #ifndef sg__lseek
 #ifdef _WIN32
 #ifdef _WIN64
 #define sg__lseek _lseeki64
-#else
+#else /* _WIN64 */
 #define sg__lseek _lseek
-#endif
-#else
+#endif /* _WIN64 */
+#else /* _WIN32 */
 #define sg__lseek lseek
-#endif
-#endif
+#endif /* _WIN32 */
+#endif /* sg__lseek */
 
 #ifndef SG__ZLIB_CHUNK
 #define SG__ZLIB_CHUNK 16384 /* 16k */
-#endif
-
-/* used by utstring library */
-#ifndef utstring_oom
-#define utstring_oom() (void) 0
-#endif
-
-/* used by uthash library */
-#ifndef HASH_NONFATAL_OOM
-#define HASH_NONFATAL_OOM 1
-#endif
-#ifndef uthash_malloc
-#define uthash_malloc(sz) sg_malloc(sz)
-#endif
-#ifndef uthash_free
-#define uthash_free(ptr, sz) sg_free(ptr)
-#endif
+#endif /* SG__ZLIB_CHUNK */
 
 #endif /* SG_MACROS_H */

+ 3 - 3
src/sg_router.c

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -63,9 +63,9 @@ int sg_router_dispatch2(struct sg_router *router, const char *path,
     }
 #ifdef PCRE2_JIT_SUPPORT
 #define SG__PCRE2_MATCH pcre2_jit_match
-#else
+#else /* PCRE2_JIT_SUPPORT */
 #define SG__PCRE2_MATCH pcre2_match
-#endif
+#endif /* PCRE2_JIT_SUPPORT */
     route->rc = SG__PCRE2_MATCH(route->re, (PCRE2_SPTR) path, strlen(path), 0,
                                 0, route->match, NULL);
 #undef SG__PCRE2_MATCH

+ 4 - 4
src/sg_routes.c

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -31,7 +31,7 @@
 #include "utlist.h"
 #ifdef _WIN32
 #include "sg_utils.h"
-#endif
+#endif /* _WIN32 */
 #include "sg_routes.h"
 #include "sg_utils.h"
 #include "sagui.h"
@@ -80,7 +80,7 @@ static struct sg_route *sg__route_new(const char *pattern, char *errmsg,
     *errnum = EINVAL;
     goto error;
   }
-#endif
+#endif /* PCRE2_JIT_SUPPORT */
   route->match = pcre2_match_data_create_from_pattern(route->re, NULL);
   if (!route->match) {
     strncpy(errmsg, _("Cannot allocate match data from the pattern.\n"),
@@ -242,7 +242,7 @@ bool sg_routes_add(struct sg_route **routes, const char *pattern,
   if (ret == EINVAL || ret == EALREADY)
     sg_strerror(ret, err, sizeof(err));
   sg__err_cb(NULL, err);
-#endif
+#endif /* SG_TESTING */
   errno = ret;
   return false;
 }

+ 2 - 2
src/sg_str.c

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -60,7 +60,7 @@ int sg_str_printf_va(struct sg_str *str, const char *fmt, va_list ap) {
 #if !defined(__arm__) && !defined(__aarch64__)
   if (!ap)
     return EINVAL;
-#endif
+#endif /* !__arm__ && !__aarch64__ */
   utstring_printf_va(str->buf, fmt, ap);
   return 0;
 }

+ 1 - 1
src/sg_strmap.c

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public

+ 31 - 24
src/sg_utils.c

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -37,16 +37,16 @@
 #include <winsock2.h>
 #include <windows.h>
 #include <wchar.h>
-#else
+#else /* _WIN32 */
 #include <sys/socket.h>
 #include <netinet/in.h>
 #ifdef HAVE_ARPA_INET_H
 #include <arpa/inet.h>
-#endif
-#endif
+#endif /* HAVE_ARPA_INET_H */
+#endif /* _WIN32 */
 #ifndef HAVE_INET_NTOP
 #include "inet.h"
-#endif
+#endif /* HAVE_INET_NTOP */
 #include "sagui.h"
 #include "sg_utils.h"
 
@@ -111,26 +111,33 @@ static char *wtos(const wchar_t *str) {
 int sg__rename(const char *old, const char *new) {
   int ret = 0;
   wchar_t *o, *n;
+  if (*old == '\0' || *new == '\0') {
+    errno = ENOENT;
+    return -1;
+  }
   o = stow(old);
-  if (!o)
-    return ENOMEM;
+  if (!o) {
+    errno = ENOMEM;
+    return -1;
+  }
   n = stow(new);
   if (!n) {
-    ret = ENOMEM;
-    goto done;
+    sg_free(o);
+    errno = ENOMEM;
+    return -1;
   }
   ret = _wrename(o, n);
   sg_free(n);
-done:
   sg_free(o);
   return ret;
 }
 
-#endif
+#endif /* _WIN32 */
 
-#if defined(_WIN32) || defined(__ANDROID__)
+#if defined(_WIN32) || defined(__ANDROID__) ||                                 \
+  (defined(__linux__) && !defined(__gnu_linux__))
 
-char *basename(const char *path) {
+char *sg__basename(const char *path) {
   char *s1 = strrchr(path, '/');
   char *s2 = strrchr(path, '\\');
   if (s1 && s2)
@@ -142,7 +149,7 @@ char *basename(const char *path) {
   return (char *) path;
 }
 
-#endif
+#endif /* _WIN32 || __ANDROID__ || (__linux__ && !__gnu_linux__) */
 
 char *sg__strdup(const char *str) {
   return str ? strdup(str) : NULL;
@@ -258,34 +265,34 @@ char *sg_strerror(int errnum, char *errmsg, size_t errlen) {
 #if defined(_WIN32) || defined(__ANDROID__) ||                                 \
   (defined(__linux__) && !defined(__gnu_linux__))
   int saved_errno;
-#else
+#else /* _WIN32 || __ANDROID__ || (__linux__ && !__gnu_linux__) */
   char *res;
-#endif
+#endif /* _WIN32 || __ANDROID__ || (__linux__ && !__gnu_linux__) */
   if (!errmsg || errlen < 1)
     return NULL;
 #if defined(_WIN32) || defined(__ANDROID__) ||                                 \
   (defined(__linux__) && !defined(__gnu_linux__))
   saved_errno = errno;
-#else
+#else /* _WIN32 || __ANDROID__ || (__linux__ && !__gnu_linux__) */
   res = strerror_r(errnum, errmsg, errlen - 1);
   memcpy(errmsg, res, errlen - 1);
   errmsg[errlen - 1] = '\0';
   return errmsg;
-#endif
+#endif /* _WIN32 || __ANDROID__ || (__linux__ && !__gnu_linux__) */
 #ifdef _WIN32
   errnum = strerror_s(errmsg, errlen, errnum);
   errno = saved_errno;
   if ((errnum != 0) && (errnum != EINVAL))
     return "?";
   return errmsg;
-#endif
+#endif /* _WIN32 */
 #if defined(__ANDROID__) || (defined(__linux__) && !defined(__gnu_linux__))
   errnum = strerror_r(errnum, errmsg, errlen);
   errno = saved_errno;
   if ((errnum != 0) && (errnum != EINVAL) && (errnum != ERANGE))
     return "?";
   return errmsg;
-#endif
+#endif /* __ANDROID__ || (__linux__ && !__gnu_linux__) */
 }
 
 bool sg_is_post(const char *method) {
@@ -339,7 +346,7 @@ char *sg_tmpdir() {
     path[len] = L'\0';
   }
   return wtos(path);
-#else
+#else /* _WIN32 */
   char *buf;
   size_t len;
 #define SG__TMPDIR_TRY_ENV(name)                                               \
@@ -355,9 +362,9 @@ char *sg_tmpdir() {
 #undef SG__TMPDIR_TRY_ENV
 #ifdef __ANDROID__
   buf = "/data/local/tmp";
-#else
+#else /* __ANDROID__ */
   buf = "/tmp";
-#endif
+#endif /* __ANDROID__ */
 done:
   buf = strdup(buf);
   if (!buf)
@@ -367,7 +374,7 @@ done:
     len--;
   buf[len] = '\0';
   return buf;
-#endif
+#endif /* _WIN32 */
 }
 
 /* Sockets */

+ 13 - 7
src/sg_utils.h

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -29,7 +29,7 @@
 
 #ifndef _WIN32
 #include <stdlib.h>
-#endif
+#endif /* _WIN32 */
 #include <stddef.h>
 #include <stdbool.h>
 #include "sg_macros.h"
@@ -44,7 +44,7 @@ struct sg__memory_manager {
 #ifdef _WIN32
 #ifndef PATH_MAX
 #define PATH_MAX _MAX_PATH
-#endif
+#endif /* PATH_MAX */
 
 #define realpath(n, r) _fullpath((r), (n), PATH_MAX)
 
@@ -52,14 +52,20 @@ SG__EXTERN char *strndup(const char *s, size_t n);
 
 SG__EXTERN int sg__rename(const char *old, const char *new);
 
-#else
+#else /* _WIN32 */
 #define sg__rename rename
-#endif
+#endif /* _WIN32 */
 
 #if defined(_WIN32) || defined(__ANDROID__) ||                                 \
   (defined(__linux__) && !defined(__gnu_linux__))
-char *basename(const char *path);
-#endif
+
+SG__EXTERN char *sg__basename(const char *path);
+
+#else /* _WIN32 || __ANDROID__ || (__linux__ && !__gnu_linux__) */
+
+#define sg__basename basename
+
+#endif /* _WIN32 || __ANDROID__ || (__linux__ && !__gnu_linux__) */
 
 SG__EXTERN char *sg__strdup(const char *str);
 

+ 20 - 13
test/CMakeLists.txt

@@ -26,7 +26,7 @@
 #
 # Cross-platform library which helps to develop web servers or frameworks.
 #
-# Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+# Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
 #
 # Sagui library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -52,6 +52,7 @@ if(BUILD_TESTING)
   add_definitions(-DSG_TESTING=1)
   if(WIN32)
     set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
+    add_definitions(-DBINARY_DIR="${CMAKE_BINARY_DIR}")
   endif()
   if(CURL_VERSION_STRING AND CURL_LIBRARY)
     string(COMPARE GREATER_EQUAL ${CURL_VERSION_STRING} "7.56" _curl_found)
@@ -68,21 +69,26 @@ if(BUILD_TESTING)
     add_definitions(-DSG_ANDROID_TESTS_DEST_DIR="${SG_ANDROID_TESTS_DEST_DIR}")
   endif()
   list(
-    APPEND SG_TESTS
-           utils
-           extra
-           str
-           strmap
-           httpauth
-           httpuplds
-           httpreq
-           httpres
-           httpsrv)
+    APPEND
+    SG_TESTS
+    utils
+    extra
+    str
+    strmap
+    httpauth
+    httpuplds
+    httpreq
+    httpres
+    httpsrv)
   if(SG_PATH_ROUTING)
     list(APPEND SG_TESTS entrypoint entrypoints routes router)
   endif()
   if(_curl_found)
-    add_definitions(-DCURL_STATICLIB=1)
+    if(MINGW)
+      list(APPEND _libs libcurl.a)
+    else()
+      add_definitions(-DCURL_STATICLIB=1)
+    endif()
     list(APPEND SG_TESTS httpsrv_curl)
     if(SG_HTTPS_SUPPORT AND GNUTLS_FOUND)
       list(APPEND SG_TESTS httpsrv_tls_curl)
@@ -106,7 +112,8 @@ if(BUILD_TESTING)
       target_include_directories(test_${_test} PUBLIC ${SG_SOURCE_DIR})
       if(ANDROID)
         add_custom_command(
-          TARGET test_${_test} POST_BUILD
+          TARGET test_${_test}
+          POST_BUILD
           COMMAND ${SG_ANDROID_ADB_COMMAND} shell mkdir -p
                   ${SG_ANDROID_TESTS_DEST_DIR}
           COMMAND ${SG_ANDROID_ADB_COMMAND} push test_${_test}

+ 10 - 9
test/sg_assert.h

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -30,8 +30,9 @@
 #ifndef _WIN32
 #ifndef _GNU_SOURCE
 #define _GNU_SOURCE
-#endif
-#endif
+#endif /* _GNU_SOURCE */
+#endif /* _WIN32 */
+
 #include <stdlib.h>
 #include <stdio.h>
 #include <errno.h>
@@ -39,17 +40,17 @@
 
 #ifdef _WIN32
 #define __progname __argv[0]
-#else
+#else /* _WIN32 */
 #if defined(__USE_GNU) && !defined(__i386__) && !defined(__ANDROID__)
 #define __progname program_invocation_short_name
-#else
+#else /* __USE_GNU && !__i386__ && !__ANDROID__ */
 extern const char *__progname;
-#endif
-#endif
+#endif /* __USE_GNU && !__i386__ && !__ANDROID__ */
+#endif /* _WIN32 */
 
 #ifdef NDEBUG
 #define ASSERT(expr) ((void) 0)
-#else
+#else /* NDEBUG */
 #define ASSERT(expr)                                                           \
   do {                                                                         \
     if (!(expr)) {                                                             \
@@ -60,6 +61,6 @@ extern const char *__progname;
       exit(EXIT_FAILURE);                                                      \
     }                                                                          \
   } while (0)
-#endif
+#endif /* NDEBUG */
 
 #endif /* SG_ASSERT_H */

+ 4 - 4
test/test_extra.c

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -30,7 +30,7 @@
 #include <microhttpd.h>
 #ifdef SG_HTTP_COMPRESSION
 #include "zlib.h"
-#endif
+#endif /* SG_HTTP_COMPRESSION */
 #include "sg_macros.h"
 #include "sg_strmap.h"
 #include "sg_extra.h"
@@ -197,7 +197,7 @@ static void test__zdeflate(void) {
   free(dest);
 }
 
-#endif
+#endif /* SG_HTTP_COMPRESSION */
 
 int main(void) {
   test__convals_iter();
@@ -206,6 +206,6 @@ int main(void) {
 #ifdef SG_HTTP_COMPRESSION
   test__zcompress();
   test__zdeflate();
-#endif
+#endif /* SG_HTTP_COMPRESSION */
   return EXIT_SUCCESS;
 }

+ 45 - 7
test/test_httpreq.c

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -30,10 +30,11 @@
 #include <string.h>
 #include "sg_httpreq.h"
 
-static void test__httpreq_new(void) {
+static void test__httpreq_new(struct sg_httpsrv *srv) {
   struct MHD_Connection *con = sg_alloc(64);
-  struct sg_httpreq *req = sg__httpreq_new(con, "abc", "def", "ghi");
+  struct sg_httpreq *req = sg__httpreq_new(srv, con, "abc", "def", "ghi");
   ASSERT(req);
+  ASSERT(req->srv == srv);
   ASSERT(strcmp(req->version, "abc") == 0);
   ASSERT(strcmp(req->method, "def") == 0);
   ASSERT(strcmp(req->path, "ghi") == 0);
@@ -45,6 +46,33 @@ static void test__httpreq_free(void) {
   sg__httpreq_free(NULL);
 }
 
+static void dummy_httpreq_cb(void *cls, struct sg_httpreq *req,
+                             struct sg_httpres *res) {
+  (void) cls;
+  (void) req;
+  (void) res;
+}
+
+static void test_httpreq_srv(struct sg_httpreq *req) {
+  struct sg_httpsrv *srv;
+  errno = 0;
+  ASSERT(!sg_httpreq_srv(NULL));
+  ASSERT(errno == EINVAL);
+
+  ASSERT(sg_httpreq_srv(req));
+
+  errno = 0;
+  req->srv = NULL;
+  ASSERT(!sg_httpreq_srv(req));
+  ASSERT(errno == 0);
+
+  srv = sg_httpsrv_new(dummy_httpreq_cb, NULL);
+  req->srv = srv;
+  ASSERT(sg_httpreq_srv(req) == srv);
+  sg_httpsrv_free(srv);
+  ASSERT(errno == 0);
+}
+
 static void test_httpreq_headers(struct sg_httpreq *req) {
   struct sg_strmap **headers;
   errno = 0;
@@ -243,7 +271,13 @@ static void test_httpreq_tls_session(void) {
   /* more tests in `test_httpsrv_tls_curl.c`. */
 }
 
-#endif
+#endif /* SG_HTTPS_SUPPORT */
+
+static void test_httpreq_isolate(struct sg_httpreq *req) {
+  ASSERT(sg_httpreq_isolate(NULL, dummy_httpreq_cb, NULL) == EINVAL);
+  ASSERT(sg_httpreq_isolate(req, NULL, NULL) == EINVAL);
+  /* more tests in `test_httpsrv_curl.c`. */
+}
 
 static void test_httpreq_set_user_data(struct sg_httpreq *req) {
   const char *dummy = "foo";
@@ -272,9 +306,11 @@ static void test_httpreq_user_data(struct sg_httpreq *req) {
 }
 
 int main(void) {
-  struct sg_httpreq *req = sg__httpreq_new(NULL, NULL, NULL, NULL);
-  test__httpreq_new();
+  struct sg_httpsrv *srv = sg_httpsrv_new(dummy_httpreq_cb, NULL);
+  struct sg_httpreq *req = sg__httpreq_new(srv, NULL, NULL, NULL, NULL);
+  test__httpreq_new(srv);
   test__httpreq_free();
+  test_httpreq_srv(req);
   test_httpreq_headers(req);
   test_httpreq_cookies(req);
   test_httpreq_params(req);
@@ -288,9 +324,11 @@ int main(void) {
   test_httpreq_client();
 #ifdef SG_HTTPS_SUPPORT
   test_httpreq_tls_session();
-#endif
+#endif /* SG_HTTPS_SUPPORT */
+  test_httpreq_isolate(req);
   test_httpreq_set_user_data(req);
   test_httpreq_user_data(req);
   sg__httpreq_free(req);
+  sg_httpsrv_free(srv);
   return EXIT_SUCCESS;
 }

+ 8 - 80
test/test_httpres.c

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -35,14 +35,14 @@
 #ifndef TEST_HTTPRES_BASE_PATH
 #ifdef __ANDROID__
 #define TEST_HTTPRES_BASE_PATH SG_ANDROID_TESTS_DEST_DIR "/"
-#else
+#else /* __ANDROID__ */
 #ifdef _WIN32
-#define TEST_HTTPRES_BASE_PATH ""
-#else
+#define TEST_HTTPRES_BASE_PATH BINARY_DIR "/"
+#else /* _WIN32 */
 #define TEST_HTTPRES_BASE_PATH "/tmp/"
-#endif
-#endif
-#endif
+#endif /* _WIN32 */
+#endif /* __ANDROID__ */
+#endif /* TEST_HTTPRES_BASE_PATH */
 
 static ssize_t dummy_read_cb(void *handle, uint64_t offset, char *buf,
                              size_t size) {
@@ -195,17 +195,9 @@ static void test_httpres_download(struct sg_httpres *res) {
   ASSERT(sg_httpres_download(NULL, PATH) == EINVAL);
   ASSERT(sg_httpres_download(res, NULL) == EINVAL);
 
-#ifdef _WIN32
-  ASSERT(sg_httpres_download(res, "") == EACCES);
-#else
   ASSERT(sg_httpres_download(res, "") == ENOENT);
-#endif
   dir = sg_tmpdir();
-#ifdef _WIN32
-  ASSERT(sg_httpres_download(res, dir) == EACCES);
-#else
   ASSERT(sg_httpres_download(res, dir) == EISDIR);
-#endif
   sg_free(dir);
 
   strcpy(str, "foo");
@@ -238,17 +230,9 @@ static void test_httpres_render(struct sg_httpres *res) {
   ASSERT(sg_httpres_render(NULL, PATH) == EINVAL);
   ASSERT(sg_httpres_render(res, NULL) == EINVAL);
 
-#ifdef _WIN32
-  ASSERT(sg_httpres_render(res, "") == EACCES);
-#else
   ASSERT(sg_httpres_render(res, "") == ENOENT);
-#endif
   dir = sg_tmpdir();
-#ifdef _WIN32
-  ASSERT(sg_httpres_render(res, dir) == EACCES);
-#else
   ASSERT(sg_httpres_render(res, dir) == EISDIR);
-#endif
   sg_free(dir);
 
   strcpy(str, "foo");
@@ -295,21 +279,11 @@ static void test_httpres_sendfile2(struct sg_httpres *res) {
   ASSERT(sg_httpres_sendfile2(res, size, max_size, offset, PATH, NULL, 600) ==
          EINVAL);
 
-#ifdef _WIN32
-  ASSERT(sg_httpres_sendfile2(res, size, max_size, offset, "", NULL, 200) ==
-         EACCES);
-#else
   ASSERT(sg_httpres_sendfile2(res, size, max_size, offset, "", NULL, 200) ==
          ENOENT);
-#endif
   dir = sg_tmpdir();
-#ifdef _WIN32
-  ASSERT(sg_httpres_sendfile2(res, size, max_size, offset, dir, NULL, 200) ==
-         EACCES);
-#else
   ASSERT(sg_httpres_sendfile2(res, size, max_size, offset, dir, NULL, 200) ==
          EISDIR);
-#endif
   sg_free(dir);
 
   strcpy(str, "foo");
@@ -385,21 +359,11 @@ static void test_httpres_sendfile(struct sg_httpres *res) {
   ASSERT(sg_httpres_sendfile(res, size, max_size, offset, PATH, false, 600) ==
          EINVAL);
 
-#ifdef _WIN32
-  ASSERT(sg_httpres_sendfile(res, size, max_size, offset, "", false, 200) ==
-         EACCES);
-#else
   ASSERT(sg_httpres_sendfile(res, size, max_size, offset, "", false, 200) ==
          ENOENT);
-#endif
   dir = sg_tmpdir();
-#ifdef _WIN32
-  ASSERT(sg_httpres_sendfile(res, size, max_size, offset, dir, false, 200) ==
-         EACCES);
-#else
   ASSERT(sg_httpres_sendfile(res, size, max_size, offset, dir, false, 200) ==
          EISDIR);
-#endif
   sg_free(dir);
 
   strcpy(str, "foo");
@@ -765,17 +729,9 @@ static void test_httpres_zdownload(struct sg_httpres *res) {
   ASSERT(sg_httpres_zdownload(NULL, PATH) == EINVAL);
   ASSERT(sg_httpres_zdownload(res, NULL) == EINVAL);
 
-#ifdef _WIN32
-  ASSERT(sg_httpres_zdownload(res, "") == EACCES);
-#else
   ASSERT(sg_httpres_zdownload(res, "") == ENOENT);
-#endif
   dir = sg_tmpdir();
-#ifdef _WIN32
-  ASSERT(sg_httpres_zdownload(res, dir) == EACCES);
-#else
   ASSERT(sg_httpres_zdownload(res, dir) == EISDIR);
-#endif
   sg_free(dir);
 
   strcpy(str, "foo");
@@ -808,17 +764,9 @@ static void test_httpres_zrender(struct sg_httpres *res) {
   ASSERT(sg_httpres_zrender(NULL, PATH) == EINVAL);
   ASSERT(sg_httpres_zrender(res, NULL) == EINVAL);
 
-#ifdef _WIN32
-  ASSERT(sg_httpres_zrender(res, "") == EACCES);
-#else
   ASSERT(sg_httpres_zrender(res, "") == ENOENT);
-#endif
   dir = sg_tmpdir();
-#ifdef _WIN32
-  ASSERT(sg_httpres_zrender(res, dir) == EACCES);
-#else
   ASSERT(sg_httpres_zrender(res, dir) == EISDIR);
-#endif
   sg_free(dir);
 
   strcpy(str, "foo");
@@ -869,21 +817,11 @@ static void test_httpres_zsendfile2(struct sg_httpres *res) {
   ASSERT(sg_httpres_zsendfile2(res, -1, size, max_size, offset, PATH, NULL,
                                600) == EINVAL);
 
-#ifdef _WIN32
-  ASSERT(sg_httpres_zsendfile2(res, -1, size, max_size, offset, "", NULL,
-                               200) == EACCES);
-#else
   ASSERT(sg_httpres_zsendfile2(res, -1, size, max_size, offset, "", NULL,
                                200) == ENOENT);
-#endif
   dir = sg_tmpdir();
-#ifdef _WIN32
-  ASSERT(sg_httpres_zsendfile2(res, -1, size, max_size, offset, dir, NULL,
-                               200) == EACCES);
-#else
   ASSERT(sg_httpres_zsendfile2(res, -1, size, max_size, offset, dir, NULL,
                                200) == EISDIR);
-#endif
   sg_free(dir);
 
   strcpy(str, "foo");
@@ -961,21 +899,11 @@ static void test_httpres_zsendfile(struct sg_httpres *res) {
   ASSERT(sg_httpres_zsendfile(res, size, max_size, offset, PATH, false, 600) ==
          EINVAL);
 
-#ifdef _WIN32
-  ASSERT(sg_httpres_zsendfile(res, size, max_size, offset, "", false, 200) ==
-         EACCES);
-#else
   ASSERT(sg_httpres_zsendfile(res, size, max_size, offset, "", false, 200) ==
          ENOENT);
-#endif
   dir = sg_tmpdir();
-#ifdef _WIN32
-  ASSERT(sg_httpres_zsendfile(res, size, max_size, offset, dir, false, 200) ==
-         EACCES);
-#else
   ASSERT(sg_httpres_zsendfile(res, size, max_size, offset, dir, false, 200) ==
          EISDIR);
-#endif
   sg_free(dir);
 
   strcpy(str, "foo");
@@ -1059,7 +987,7 @@ int main(void) {
   test_httpres_zrender(res);
   test_httpres_zsendfile2(res);
   test_httpres_zsendfile(res);
-#endif
+#endif /* SG_HTTP_COMPRESSION */
   test_httpres_clear(res);
   sg__httpres_free(res);
   return EXIT_SUCCESS;

+ 29 - 9
test/test_httpsrv.c

@@ -28,7 +28,7 @@
 
 #ifndef TEST_HTTPSRV_PORT
 #define TEST_HTTPSRV_PORT 8080
-#endif
+#endif /* TEST_HTTPSRV_PORT */
 
 #include "sg_assert.h"
 
@@ -224,7 +224,9 @@ static void test__httpsrv_ahc(struct sg_httpsrv *srv) {
 }
 
 static void test__httpsrv_rcc(void) {
-  struct sg_httpreq *req = sg__httpreq_new(NULL, NULL, NULL, NULL);
+  struct sg_httpsrv *srv = sg_httpsrv_new(dummy_httpreq_cb, NULL);
+  struct sg_httpreq *req = sg__httpreq_new(srv, NULL, NULL, NULL, NULL);
+  sg_httpsrv_free(srv);
   sg__httpsrv_rcc(NULL, NULL, (void **) &req,
                   MHD_REQUEST_TERMINATED_COMPLETED_OK);
   ASSERT(!req);
@@ -298,11 +300,11 @@ static void test_httpsrv_new2(void) {
   ASSERT(srv->post_buf_size == 1024);
   ASSERT(srv->payld_limit == 1048576);
   ASSERT(srv->uplds_limit == 16777216);
-#else
+#else /* __arm__ */
   ASSERT(srv->post_buf_size == 4096);
   ASSERT(srv->payld_limit == 4194304);
   ASSERT(srv->uplds_limit == 67108864);
-#endif
+#endif /* __arm__ */
   sg_httpsrv_free(srv);
 }
 
@@ -403,7 +405,7 @@ static void test_httpsrv_listen(struct sg_httpsrv *srv) {
   ASSERT(!sg_httpsrv_listen(dummy_srv, TEST_HTTPSRV_PORT, true));
   ASSERT(errno == EADDRINUSE);
   sg_httpsrv_free(dummy_srv);
-#endif
+#endif /* __linux__ */
 }
 
 #ifdef SG_HTTPS_SUPPORT
@@ -504,7 +506,7 @@ static void test_httpsrv_tls_listen(struct sg_httpsrv *srv) {
                                 TEST_HTTPSRV_PORT, true));
   ASSERT(errno == EADDRINUSE);
   sg_httpsrv_free(dummy_srv);
-#endif
+#endif /* __linux__ */
   ASSERT(sg_httpsrv_shutdown(srv) == 0);
   ASSERT(sg_httpsrv_listen(srv, 0, false));
   ASSERT(sg_httpsrv_shutdown(srv) == 0);
@@ -521,14 +523,15 @@ static void test_httpsrv_tls_listen2(struct sg_httpsrv *srv) {
   ASSERT(errno == EINVAL);
 }
 
-#endif
+#endif /* SG_HTTPS_SUPPORT */
 
 static void test_httpsrv_shutdown(struct sg_httpsrv *srv) {
   ASSERT(sg_httpsrv_shutdown(NULL) == EINVAL);
 
+  ASSERT(sg_httpsrv_listen(srv, 0, false));
   ASSERT(sg_httpsrv_shutdown(srv) == 0);
   ASSERT(!srv->handle);
-  ASSERT(sg_httpsrv_shutdown(srv) == 0);
+  ASSERT(sg_httpsrv_shutdown(srv) == EALREADY);
   ASSERT(sg_httpsrv_listen(srv, TEST_HTTPSRV_PORT, true));
 }
 
@@ -748,6 +751,22 @@ static void test_httpsrv_con_limit(struct sg_httpsrv *srv) {
   ASSERT(errno == 0);
 }
 
+static void test_httpsrv_handle(struct sg_httpsrv *srv) {
+  void *fake_handle = (void *) 123;
+  void *old_handle;
+
+  errno = 0;
+  ASSERT(sg_httpsrv_handle(NULL) == 0);
+  ASSERT(errno == EINVAL);
+
+  errno = 0;
+  old_handle = srv->handle;
+  srv->handle = fake_handle;
+  ASSERT(sg_httpsrv_handle(srv) == fake_handle);
+  ASSERT(errno == 0);
+  srv->handle = old_handle;
+}
+
 int main(void) {
   struct sg_httpsrv *srv = sg_httpsrv_new(dummy_httpreq_cb, NULL);
   /* test__httperr_cb() */
@@ -763,7 +782,7 @@ int main(void) {
 #ifdef SG_HTTPS_SUPPORT
   test_httpsrv_tls_listen(srv);
   test_httpsrv_tls_listen2(srv);
-#endif
+#endif /* SG_HTTPS_SUPPORT */
   test_httpsrv_shutdown(srv);
   test_httpsrv_port(srv);
   test_httpsrv_is_threaded(srv);
@@ -783,6 +802,7 @@ int main(void) {
   test_httpsrv_con_timeout(srv);
   test_httpsrv_set_con_limit(srv);
   test_httpsrv_con_limit(srv);
+  test_httpsrv_handle(srv);
   sg_httpsrv_free(srv);
   return EXIT_SUCCESS;
 }

+ 55 - 30
test/test_httpsrv_curl.c

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -29,6 +29,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdbool.h>
+#include <unistd.h>
 #include <sys/stat.h>
 #include <curl/curl.h>
 #include <sagui.h>
@@ -43,7 +44,7 @@
 
 #ifndef TEST_HTTPSRV_CURL_PORT
 #define TEST_HTTPSRV_CURL_PORT 8080
-#endif
+#endif /* TEST_HTTPSRV_CURL_PORT */
 
 #ifndef TEST_HTTPSRV_CURL_BASE_PATH
 #ifdef __ANDROID__
@@ -51,11 +52,11 @@
 #else
 #ifdef _WIN32
 #define TEST_HTTPSRV_CURL_BASE_PATH ""
-#else
+#else /* _WIN32 */
 #define TEST_HTTPSRV_CURL_BASE_PATH "/tmp/"
-#endif
-#endif
-#endif
+#endif /* _WIN32 */
+#endif /* __ANDROID__ */
+#endif /* TEST_HTTPSRV_CURL_BASE_PATH */
 
 #define OK_MSG "libsagui [OK]"
 #define ERROR_MSG "libsagui [ERROR]"
@@ -92,22 +93,28 @@ static char *ftos(const char *filename) {
   return str;
 }
 
-#endif
+#endif /* SG_HTTP_COMPRESSION */
 
-static ssize_t sg_httpres_stream_read_cb(void *handle,
-                                         __SG_UNUSED uint64_t offset, char *buf,
-                                         size_t size) {
+static ssize_t httpres_stream_read_cb(void *handle, __SG_UNUSED uint64_t offset,
+                                      char *buf, size_t size) {
   ssize_t have = fread(buf, 1, size, handle);
   if ((have == 0) || (have < 0))
     return sg_eor(have < 0);
   return have;
 }
 
-static void sg_httpres_stream_free_cb(void *handle) {
+static void httpres_stream_free_cb(void *handle) {
   ASSERT(fclose(handle) == 0);
 }
 
-static bool srv_auth_cb(__SG_UNUSED void *cls, struct sg_httpauth *auth,
+static void httpreq_isolated_cb(__SG_UNUSED void *cls,
+                                __SG_UNUSED struct sg_httpreq *req,
+                                struct sg_httpres *res) {
+  usleep(1000 * 500);
+  sg_httpres_send(res, OK_MSG, "text/plain", 200);
+}
+
+static bool httpauth_cb(__SG_UNUSED void *cls, struct sg_httpauth *auth,
                         struct sg_httpreq *req,
                         __SG_UNUSED struct sg_httpres *res) {
   char *data = strdup("abc123");
@@ -128,13 +135,13 @@ static bool srv_auth_cb(__SG_UNUSED void *cls, struct sg_httpauth *auth,
   return pass;
 }
 
-static void srv_err_cb(__SG_UNUSED void *cls, const char *err) {
+static void httpsrv_err_cb(__SG_UNUSED void *cls, const char *err) {
   fprintf(stderr, "%s", err);
   fflush(stderr);
 }
 
-static void srv_req_cb(__SG_UNUSED void *cls, struct sg_httpreq *req,
-                       struct sg_httpres *res) {
+static void httpsrv_req_cb(__SG_UNUSED void *cls, struct sg_httpreq *req,
+                           struct sg_httpres *res) {
   const char *filename1 = TEST_HTTPSRV_CURL_BASE_PATH "foo_uploaded.txt";
   const char *filename2 = TEST_HTTPSRV_CURL_BASE_PATH "bar_uploaded.txt";
   const size_t len = 3;
@@ -151,7 +158,7 @@ static void srv_req_cb(__SG_UNUSED void *cls, struct sg_httpreq *req,
 #ifdef SG_HTTP_COMPRESSION
   struct stat sbuf;
   const char *header;
-#endif
+#endif /* SG_HTTP_COMPRESSION */
   data = sg_httpreq_user_data(req);
   ASSERT(data);
   ASSERT(strcmp(data, "abc123") == 0);
@@ -182,7 +189,7 @@ static void srv_req_cb(__SG_UNUSED void *cls, struct sg_httpreq *req,
         return;
       }
     }
-#endif
+#endif /* SG_HTTP_COMPRESSION */
     sg_httpres_send(res, OK_MSG, "text/plain", 200);
     return;
   }
@@ -273,7 +280,7 @@ static void srv_req_cb(__SG_UNUSED void *cls, struct sg_httpreq *req,
                                 200) == 0);
     return;
   }
-#endif
+#endif /* SG_HTTP_COMPRESSION */
 
   if (strcmp(sg_httpreq_path(req), "/data") == 0) {
     ASSERT(strcmp(sg_httpreq_method(req), "GET") == 0);
@@ -292,15 +299,15 @@ static void srv_req_cb(__SG_UNUSED void *cls, struct sg_httpreq *req,
            0);
     return;
   }
-#endif
+#endif /* SG_HTTP_COMPRESSION */
 
   if (strcmp(sg_httpreq_path(req), "/stream") == 0) {
     ASSERT(strcmp(sg_httpreq_method(req), "GET") == 0);
     ASSERT(access(filename1, F_OK) == 0);
     tmp_file = fopen(filename1, "r");
     ASSERT(tmp_file);
-    sg_httpres_sendstream(res, len, sg_httpres_stream_read_cb, tmp_file,
-                          sg_httpres_stream_free_cb, 200);
+    sg_httpres_sendstream(res, len, httpres_stream_read_cb, tmp_file,
+                          httpres_stream_free_cb, 200);
     return;
   }
 
@@ -309,11 +316,17 @@ static void srv_req_cb(__SG_UNUSED void *cls, struct sg_httpreq *req,
     ASSERT(strcmp(sg_httpreq_method(req), "GET") == 0);
     tmp_file = fopen(__FILE__, "rb");
     ASSERT(tmp_file);
-    sg_httpres_zsendstream(res, sg_httpres_stream_read_cb, tmp_file,
-                           sg_httpres_stream_free_cb, 200);
+    sg_httpres_zsendstream(res, httpres_stream_read_cb, tmp_file,
+                           httpres_stream_free_cb, 200);
+    return;
+  }
+#endif /* SG_HTTP_COMPRESSION */
+
+  if (strcmp(sg_httpreq_path(req), "/sleep") == 0) {
+    ASSERT(sg_httpreq_isolate(req, httpreq_isolated_cb, NULL) == 0);
+    ASSERT(sg_httpreq_isolate(req, httpreq_isolated_cb, NULL) == EALREADY);
     return;
   }
-#endif
 
   sg_httpres_send(res, ERROR_MSG, "text/plain", 500);
 }
@@ -343,13 +356,13 @@ int main(void) {
   char *str;
   char *tmp;
   size_t size;
-#endif
+#endif /* SG_HTTP_COMPRESSION */
   long status;
   bool auth_403;
 
   curl_global_init(CURL_GLOBAL_ALL);
 
-  srv = sg_httpsrv_new2(srv_auth_cb, srv_req_cb, srv_err_cb, &auth_403);
+  srv = sg_httpsrv_new2(httpauth_cb, httpsrv_req_cb, httpsrv_err_cb, &auth_403);
   ASSERT(srv);
   curl = curl_easy_init();
   ASSERT(curl);
@@ -427,7 +440,7 @@ int main(void) {
   ASSERT(curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &cl) ==
          CURLE_OK);
   ASSERT((curl_off_t) strlen(PAGE) > cl);
-#endif
+#endif /* SG_HTTP_COMPRESSION */
 
   snprintf(url, sizeof(url), "http://localhost:%d/cancel-auth",
            TEST_HTTPSRV_CURL_PORT);
@@ -592,7 +605,7 @@ int main(void) {
   str[size] = '\0';
   ASSERT(strcmp(sg_str_content(res), str) == 0);
   free(tmp);
-#endif
+#endif /* SG_HTTP_COMPRESSION */
 
   snprintf(url, sizeof(url), "http://localhost:%d/download?filename=%s",
            TEST_HTTPSRV_CURL_PORT, filename2);
@@ -633,7 +646,7 @@ int main(void) {
   ASSERT(status == 200);
   ASSERT(strcmp(sg_str_content(res), "fooooooooooobaaaaaaaaaarrrrrrrrrrr") ==
          0);
-#endif
+#endif /* SG_HTTP_COMPRESSION */
 
   snprintf(url, sizeof(url), "http://localhost:%d/stream",
            TEST_HTTPSRV_CURL_PORT);
@@ -664,7 +677,19 @@ int main(void) {
   ASSERT(str);
   ASSERT(strcmp(sg_str_content(res), str) == 0);
   free(str);
-#endif
+#endif /* SG_HTTP_COMPRESSION */
+
+  snprintf(url, sizeof(url), "http://localhost:%d/sleep",
+           TEST_HTTPSRV_CURL_PORT);
+  ASSERT(curl_easy_setopt(curl, CURLOPT_URL, url) == CURLE_OK);
+
+  ASSERT(sg_str_clear(res) == 0);
+  ret = curl_easy_perform(curl);
+  CURL_LOG(ret);
+  ASSERT(ret == CURLE_OK);
+  ASSERT(curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status) == CURLE_OK);
+  ASSERT(status == 200);
+  ASSERT(strcmp(sg_str_content(res), OK_MSG) == 0);
 
   ASSERT(sg_httpsrv_shutdown(srv) == 0);
 

+ 2 - 2
test/test_httpsrv_tls_curl.c

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -42,7 +42,7 @@
 
 #ifndef TEST_HTTPSRV_TLS_CURL_PORT
 #define TEST_HTTPSRV_TLS_CURL_PORT 8080
-#endif
+#endif /* TEST_HTTPSRV_TLS_CURL_PORT */
 
 #define OK_MSG "libsagui [OK]"
 

+ 16 - 16
test/test_httpuplds.c

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -37,23 +37,23 @@
 #ifndef TEST_HTTPUPLDS_BASE_PATH
 #ifdef __ANDROID__
 #define TEST_HTTPUPLDS_BASE_PATH SG_ANDROID_TESTS_DEST_DIR "/"
-#else
+#else /* __ANDROID__ */
 #ifdef _WIN32
-#define TEST_HTTPUPLDS_BASE_PATH ""
-#else
+#define TEST_HTTPUPLDS_BASE_PATH BINARY_DIR "/"
+#else /* _WIN32 */
 #define TEST_HTTPUPLDS_BASE_PATH "/tmp/"
-#endif
-#endif
-#endif
+#endif /* _WIN32 */
+#endif /* __ANDROID__ */
+#endif /* TEST_HTTPUPLDS_BASE_PATH */
 
 #ifndef TEST_HTTPUPLDS_OPEN_MODE
 #define TEST_HTTPUPLDS_OPEN_MODE                                               \
   S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH
-#endif
+#endif /* TEST_HTTPUPLDS_OPEN_MODE */
 
 #ifndef TEST_HTTPUPLDS_OPEN_WFLAGS
 #define TEST_HTTPUPLDS_OPEN_WFLAGS O_RDWR | O_CREAT | O_TRUNC
-#endif
+#endif /* TEST_HTTPUPLDS_OPEN_WFLAGS */
 
 static int dummy_httpuplds_save_cb(void *handle, bool overwritten) {
   (void) handle;
@@ -115,7 +115,7 @@ static void test__httpuplds_add(void) {
   char err[256];
   struct sg_httpsrv *srv =
     sg_httpsrv_new2(NULL, dummy_httpreq_cb, dummy_err_cb, err);
-  struct sg_httpreq *req = sg__httpreq_new(NULL, "", "", "");
+  struct sg_httpreq *req = sg__httpreq_new(srv, NULL, "", "", "");
   ASSERT(sg_httpuplds_count(sg_httpreq_uploads(req)) == 0);
   sg__httpuplds_add(srv, req, "abc", "def", "ghi", "jkl");
   ASSERT(strcmp(sg_httpupld_field(req->curr_upld), "abc") == 0);
@@ -138,7 +138,7 @@ static void test__httpuplds_iter(void) {
   char err[256], str[256];
   struct sg_httpsrv *srv =
     sg_httpsrv_new2(NULL, dummy_httpreq_cb, dummy_err_cb, err);
-  struct sg_httpreq *req = sg__httpreq_new(NULL, "", "", "");
+  struct sg_httpreq *req = sg__httpreq_new(srv, NULL, "", "", "");
   struct sg__httpupld_holder holder = {srv, req};
   struct sg_strmap **fields;
   sg_httpupld_cb saved_upld_cb;
@@ -206,7 +206,7 @@ static void test__httpuplds_process(void) {
   struct MHD_Connection *con = sg_alloc(64);
   struct sg_httpsrv *srv =
     sg_httpsrv_new2(NULL, dummy_httpreq_cb, dummy_err_cb, err);
-  struct sg_httpreq *req = sg__httpreq_new(NULL, "", "", "");
+  struct sg_httpreq *req = sg__httpreq_new(srv, NULL, "", "", "");
   int ret = 0;
   size_t size = 0;
 
@@ -235,7 +235,7 @@ static void test__httpuplds_process(void) {
 
 static void test__httpuplds_cleanup(void) {
   struct sg_httpsrv *srv = sg_httpsrv_new(dummy_httpreq_cb, NULL);
-  struct sg_httpreq *req = sg__httpreq_new(NULL, "", "", "");
+  struct sg_httpreq *req = sg__httpreq_new(srv, NULL, "", "", "");
   struct sg_httpupld *tmp;
   size_t count = 0;
   ASSERT(srv);
@@ -303,7 +303,7 @@ static void test__httpupld_cb(void) {
            _("Cannot create temporary upload file in \"%s\": %s.\n"), "/",
            strerror(EACCES));
   ASSERT(strcmp(err, str) == 0);
-#endif
+#endif /* __linux__ && !__ANDROID__ */
 
   dir = sg_tmpdir();
   dest_path = sg__strjoin(PATH_SEP, dir, filename);
@@ -372,7 +372,7 @@ static void test__httpupld_free_cb(void) {
   ASSERT(handle->fd > -1);
 #ifdef _WIN32
   ASSERT(close(handle->fd) == 0);
-#endif
+#endif /* _WIN32 */
   ASSERT(unlink(handle->path) == 0);
   ASSERT(strcmp(err, "") == 0);
   saved_srv = handle->srv;
@@ -426,8 +426,8 @@ static void test__httpupld_save_as_cb(void) {
   handle->fd =
     open(handle->path, TEST_HTTPUPLDS_OPEN_WFLAGS, TEST_HTTPUPLDS_OPEN_MODE);
   ASSERT(handle->fd > -1);
-  ASSERT(write(handle->fd, "foo", len) == len);
   ASSERT(sg__httpupld_save_as_cb(handle, NULL, false) == EINVAL);
+  close(handle->fd);
 
   handle->fd =
     open(bar_path, TEST_HTTPUPLDS_OPEN_WFLAGS, TEST_HTTPUPLDS_OPEN_MODE);

+ 4 - 4
test/test_routes.c

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -31,7 +31,7 @@
 #include <stdlib.h>
 #ifdef PCRE2_JIT_SUPPORT
 #include <stdint.h>
-#endif
+#endif /* PCRE2_JIT_SUPPORT */
 #include <errno.h>
 #include "sg_macros.h"
 #include "pcre2.h"
@@ -101,7 +101,7 @@ static void test__route_new(void) {
   char err[SG_ERR_SIZE], errmsg[SG_ERR_SIZE];
 #ifdef PCRE2_JIT_SUPPORT
   uint32_t opt;
-#endif
+#endif /* PCRE2_JIT_SUPPORT */
   int errnum;
   ASSERT(!sg__route_new("some\\Kpattern", err, sizeof(err), &errnum, route_cb,
                         "foo"));
@@ -128,7 +128,7 @@ static void test__route_new(void) {
 #ifdef PCRE2_JIT_SUPPORT
   ASSERT(pcre2_config(PCRE2_CONFIG_JIT, &opt) == 0);
   ASSERT(opt == 1);
-#endif
+#endif /* PCRE2_JIT_SUPPORT */
 
   ASSERT((route = sg__route_new("pattern", err, sizeof(err), &errnum, route_cb,
                                 "foo")));

+ 2 - 2
test/test_str.c

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -46,7 +46,7 @@ static void test_str_printf_va(struct sg_str *str, const char *fmt,
   ASSERT(sg_str_printf_va(str, NULL, ap) == EINVAL);
 #ifndef __arm__
   ASSERT(sg_str_printf_va(str, fmt, NULL) == EINVAL);
-#endif
+#endif /* __arm__ */
 
   sg_str_clear(str);
   sg_str_printf_va(str, fmt, ap);

+ 7 - 7
test/test_utils.c

@@ -7,7 +7,7 @@
  *
  * Cross-platform library which helps to develop web servers or frameworks.
  *
- * Copyright (C) 2016-2019 Silvio Clecio <[email protected]>
+ * Copyright (C) 2016-2020 Silvio Clecio <[email protected]>
  *
  * Sagui library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -33,9 +33,9 @@
 #include <ws2tcpip.h>
 #include <winsock2.h>
 #include <windows.h>
-#else
+#else /* _WIN32 */
 #include <sys/socket.h>
-#endif
+#endif /* _WIN32 */
 #include "sg_macros.h"
 #include "sg_utils.h"
 #include <sagui.h>
@@ -422,7 +422,7 @@ static void test_tmpdir(void) {
 #ifdef _WIN32
   char path[MAX_PATH + 1];
   size_t len;
-#endif
+#endif /* _WIN32 */
   char *tmp = sg_tmpdir();
   ASSERT(tmp != NULL);
 #if defined(_WIN32)
@@ -432,11 +432,11 @@ static void test_tmpdir(void) {
     path[len] = '\0';
   }
   ASSERT(strcmp(tmp, path) == 0);
-#elif defined(__ANDROID__)
+#elif defined(__ANDROID__) /* _WIN32 */
   ASSERT(strcmp(tmp, "/data/local/tmp") == 0);
-#else
+#else /* __ANDROID__ */
   ASSERT(strcmp(tmp, "/tmp") == 0);
-#endif
+#endif /* _WIN32 */
   sg_free(tmp);
 }