From 48c46dfdd986ad4a7a0692d05992f7882bef6a88 Mon Sep 17 00:00:00 2001 From: Sascha Volkenandt Date: Tue, 2 Jan 2007 19:18:27 +0000 Subject: [PATCH] - initial checkin --- COPYING | 340 +++++++++++++++++ HISTORY | 6 + Makefile | 125 +++++++ README | 11 + channels.ecpp | 22 ++ httpd/Makefile | 33 ++ httpd/dispatcher.cpp | 109 ++++++ httpd/job.cpp | 240 ++++++++++++ httpd/listener.cpp | 167 +++++++++ httpd/poller.cpp | 190 ++++++++++ httpd/regex.cpp | 175 +++++++++ httpd/tnt/dispatcher.h | 121 ++++++ httpd/tnt/gcryptinit.h | 37 ++ httpd/tnt/gnutls.h | 146 ++++++++ httpd/tnt/job.h | 201 ++++++++++ httpd/tnt/listener.h | 78 ++++ httpd/tnt/openssl.h | 121 ++++++ httpd/tnt/poller.h | 61 +++ httpd/tnt/regex.h | 79 ++++ httpd/tnt/ssl.h | 52 +++ httpd/tnt/tntnet.h | 99 +++++ httpd/tnt/worker.h | 91 +++++ httpd/tntnet.cpp | 832 +++++++++++++++++++++++++++++++++++++++++ httpd/worker.cpp | 365 ++++++++++++++++++ live.cpp | 86 +++++ setup.cpp | 33 ++ setup.h | 26 ++ thread.cpp | 40 ++ thread.h | 22 ++ tntconfig.cpp | 65 ++++ tntconfig.h | 28 ++ 31 files changed, 4001 insertions(+) create mode 100644 COPYING create mode 100644 HISTORY create mode 100644 Makefile create mode 100644 README create mode 100644 channels.ecpp create mode 100644 httpd/Makefile create mode 100644 httpd/dispatcher.cpp create mode 100644 httpd/job.cpp create mode 100644 httpd/listener.cpp create mode 100644 httpd/poller.cpp create mode 100644 httpd/regex.cpp create mode 100644 httpd/tnt/dispatcher.h create mode 100644 httpd/tnt/gcryptinit.h create mode 100644 httpd/tnt/gnutls.h create mode 100644 httpd/tnt/job.h create mode 100644 httpd/tnt/listener.h create mode 100644 httpd/tnt/openssl.h create mode 100644 httpd/tnt/poller.h create mode 100644 httpd/tnt/regex.h create mode 100644 httpd/tnt/ssl.h create mode 100644 httpd/tnt/tntnet.h create mode 100644 httpd/tnt/worker.h create mode 100644 httpd/tntnet.cpp create mode 100644 httpd/worker.cpp create mode 100644 live.cpp create mode 100644 setup.cpp create mode 100644 setup.h create mode 100644 thread.cpp create mode 100644 thread.h create mode 100644 tntconfig.cpp create mode 100644 tntconfig.h diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..f90922ee --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/HISTORY b/HISTORY new file mode 100644 index 00000000..f9f21b7c --- /dev/null +++ b/HISTORY @@ -0,0 +1,6 @@ +VDR Plugin 'httpd' Revision History +----------------------------------- + +2007-01-01: Version 0.0.1 + +- Initial revision. diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..b69c1dff --- /dev/null +++ b/Makefile @@ -0,0 +1,125 @@ +# +# Makefile for a Video Disk Recorder plugin +# +# $Id: Makefile,v 1.1 2007/01/02 19:18:27 lordjaxom Exp $ + +# The official name of this plugin. +# This name will be used in the '-P...' option of VDR to load the plugin. +# By default the main source file also carries this name. +# IPORTANT: the presence of this macro is important for the Make.config +# file. So it must be defined, even if it is not used here! +# +PLUGIN = live + +### The version number of this plugin (taken from the main source file): + +VERSION = $(shell grep 'static const char \*VERSION *=' $(PLUGIN).cpp | awk '{ print $$6 }' | sed -e 's/[";]//g') + +### The C++ compiler and options: + +CXX ?= g++ +CXXFLAGS ?= -fPIC -g -O2 -Wall -Woverloaded-virtual + +ECPPC ?= /usr/local/bin/ecppc +CXXFLAGS += `tntnet-config --cxxflags` + +LDFLAGS += `tntnet-config --libs` + +### The directory environment: + +VDRDIR = ../../.. +LIBDIR = ../../lib +TMPDIR = /tmp + +### Allow user defined options to overwrite defaults: + +-include $(VDRDIR)/Make.config + +### The version number of VDR's plugin API (taken from VDR's "config.h"): + +APIVERSION = $(shell sed -ne '/define APIVERSION/s/^.*"\(.*\)".*$$/\1/p' $(VDRDIR)/config.h) + +### The name of the distribution archive: + +ARCHIVE = $(PLUGIN)-$(VERSION) +PACKAGE = vdr-$(ARCHIVE) + +### Includes and Defines (add further entries here): + +INCLUDES += -I$(VDRDIR)/include -Ihttpd + +DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"' + +SUBDIRS = httpd + +LIBS += httpd/libhttpd.a + +### The object files (add further files here): + +OBJS = $(PLUGIN).o thread.o tntconfig.o setup.o + +WEBS = channels.o + +### Implicit rules: + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $< + +%.cpp: %.ecpp + $(ECPPC) $(ECPPFLAGS) $(ECPPFLAGS_CPP) $< + +%.cpp: %.gif + $(ECPPC) $(ECPPFLAGS) $(ECPPFLAGS_GIF) -b $< + +%.cpp: %.jpg + $(ECPPC) $(ECPPFLAGS) $(ECPPFLAGS_JPG) -b $< + +%.cpp: %.css + $(ECPPC) $(ECPPFLAGS) $(ECPPFLAGS_CSS) -b $< + +%.cpp: %.js + $(ECPPC) $(ECPPFLAGS) $(ECPPFLAGS_JS) -b $< + +# Dependencies: + +MAKEDEP = $(CXX) -MM -MG +DEPFILE = .dependencies +$(DEPFILE): Makefile + @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.cpp) > $@ + +-include $(DEPFILE) + +### Targets: + +.PHONY: all dist clean SUBDIRS + +SUBDIRS: + @for dir in $(SUBDIRS); do \ + make -C $$dir CXX="$(CXX)" CXXFLAGS="$(CXXFLAGS)" lib$$dir.a ; \ + done + +all: libvdr-$(PLUGIN).so libtnt-$(PLUGIN).so + +libvdr-$(PLUGIN).so: $(OBJS) SUBDIRS + $(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $(OBJS) $(LIBS) -o $@ + @cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION) + +libtnt-$(PLUGIN).so: $(WEBS) + $(CXX) $(CXXFLAGS) $(LDFLAGS) -shared -o $@ $^ + @cp --remove-destination $@ $(LIBDIR)/$@ + +dist: clean $(WEBS:%.o=%.cpp) + @-rm -rf $(TMPDIR)/$(ARCHIVE) + @mkdir $(TMPDIR)/$(ARCHIVE) + @cp -a * $(TMPDIR)/$(ARCHIVE) + @tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE) + @-rm -rf $(TMPDIR)/$(ARCHIVE) + @echo Distribution package created as $(PACKAGE).tgz + +clean: + @-rm -f $(OBJS) $(WEBS) $(DEPFILE) *.so *.tgz core* *~ + @for dir in $(SUBDIRS); do \ + make -C $$dir clean ; \ + done + + diff --git a/README b/README new file mode 100644 index 00000000..4e04b83a --- /dev/null +++ b/README @@ -0,0 +1,11 @@ +This is a "plugin" for the Video Disk Recorder (VDR). + +Written by: Your Name + +Project's homepage: URL + +Latest version available at: URL + +See the file COPYING for license information. + +Description: diff --git a/channels.ecpp b/channels.ecpp new file mode 100644 index 00000000..6458f5ba --- /dev/null +++ b/channels.ecpp @@ -0,0 +1,22 @@ +<%pre> +#include + + + + ecpp-application testproject + + +<{ + + for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) { + if (!channel->GroupSep() && *channel->Name()) { +}> + <$ channel->Name() $> +<{ + } + } + + +}> + + diff --git a/httpd/Makefile b/httpd/Makefile new file mode 100644 index 00000000..ffa7f0bd --- /dev/null +++ b/httpd/Makefile @@ -0,0 +1,33 @@ +CXX ?= g++ +AR ?= ar + +CXXFLAGS ?= -O2 -Woverloaded-virtual -Wall -fPIC + +INCLUDES += -I. + +OBJS = dispatcher.o job.o regex.o worker.o \ + listener.o poller.o tntnet.o + +### Implicit rules: + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $< + +# Dependencies: + +MAKEDEP = $(CXX) -MM -MG +DEPFILE = .dependencies +$(DEPFILE): Makefile + @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.cpp) > $@ + +-include $(DEPFILE) + +### Targets: + +all: libhttpd.a + +libhttpd.a: $(OBJS) + $(AR) r $@ $^ + +clean: + rm -f *.o core* libproctools.a proctest $(DEPFILE) diff --git a/httpd/dispatcher.cpp b/httpd/dispatcher.cpp new file mode 100644 index 00000000..8620e76a --- /dev/null +++ b/httpd/dispatcher.cpp @@ -0,0 +1,109 @@ +/* dispatcher.cpp + * Copyright (C) 2003-2005 Tommi Maekitalo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and + * NON-INFRINGEMENT. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "tnt/dispatcher.h" +#include +#include +#include +#include +#include + +log_define("tntnet.dispatcher") + +namespace tnt +{ + +void Dispatcher::addUrlMapEntry(const std::string& url, const CompidentType& ci) +{ + cxxtools::WrLock lock(rwlock); + + urlmap.push_back(urlmap_type::value_type(regex(url), ci)); +} + +Compident Dispatcher::mapComp(const std::string& compUrl) const +{ + urlmap_type::const_iterator pos = urlmap.begin(); + return mapCompNext(compUrl, pos); +} + +namespace { + class regmatch_formatter : public std::unary_function + { + public: + regex_smatch what; + std::string operator() (const std::string& s) const + { return what.format(s); } + }; +} + +Dispatcher::urlMapCacheType::size_type Dispatcher::maxUrlMapCache = 8192; + +Dispatcher::CompidentType Dispatcher::mapCompNext(const std::string& compUrl, + Dispatcher::urlmap_type::const_iterator& pos) const +{ + // check cache + urlMapCacheType::key_type cacheKey = urlMapCacheType::key_type(compUrl, pos); + urlMapCacheType::const_iterator um = urlMapCache.find(cacheKey); + if (um != urlMapCache.end()) + return um->second; + + // no cache hit + regmatch_formatter formatter; + + for (; pos != urlmap.end(); ++pos) + { + if (pos->first.match(compUrl, formatter.what)) + { + const CompidentType& src = pos->second; + + CompidentType ci; + ci.libname = formatter(src.libname); + ci.compname = formatter(src.compname); + if (src.hasPathInfo()) + ci.setPathInfo(formatter(src.getPathInfo())); + std::transform(src.getArgs().begin(), src.getArgs().end(), + std::back_inserter(ci.getArgsRef()), formatter); + + // clear cache after maxUrlMapCache distict requests + if (urlMapCache.size() >= maxUrlMapCache) + { + log_warn("clear url-map-cache"); + urlMapCache.clear(); + } + + urlMapCache.insert(urlMapCacheType::value_type(cacheKey, ci)); + + return ci; + } + } + + throw NotFoundException(compUrl); +} + +Dispatcher::CompidentType Dispatcher::PosType::getNext() +{ + if (first) + first = false; + else + ++pos; + + return dis.mapCompNext(url, pos); +} + +} diff --git a/httpd/job.cpp b/httpd/job.cpp new file mode 100644 index 00000000..49ecba6f --- /dev/null +++ b/httpd/job.cpp @@ -0,0 +1,240 @@ +/* job.cpp + * Copyright (C) 2003-2005 Tommi Maekitalo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and + * NON-INFRINGEMENT. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "tnt/job.h" +#include +#include +#include +#include +#include + +log_define("tntnet.job") + +namespace tnt +{ + unsigned Job::socket_read_timeout = 200; + unsigned Job::socket_write_timeout = 10000; + unsigned Job::keepalive_max = 1000; + unsigned Job::socket_buffer_size = 16384; + + Job::~Job() + { } + + void Job::clear() + { + parser.reset(); + request.clear(); + touch(); + } + + int Job::msecToTimeout(time_t currentTime) const + { + return (lastAccessTime - currentTime + 1) * 1000 + + getKeepAliveTimeout() + - getSocketReadTimeout(); + } + + unsigned Job::getKeepAliveTimeout() + { + return HttpReply::getKeepAliveTimeout(); + } + + //////////////////////////////////////////////////////////////////////// + // Tcpjob + // + void Tcpjob::accept(const cxxtools::net::Server& listener) + { + log_debug("accept"); + socket.accept(listener); + + struct sockaddr_storage s = socket.getSockAddr(); + struct sockaddr_storage sockaddr; + memcpy(&sockaddr, &s, sizeof(sockaddr)); + + char buffer[INET6_ADDRSTRLEN]; + log_debug("connection accepted from " + << inet_ntop(AF_INET6, &(socket.getPeeraddr()), buffer, sizeof(buffer))); + + getRequest().setPeerAddr(socket.getPeeraddr()); + getRequest().setServerAddr(sockaddr); + getRequest().setSsl(false); + } + + std::iostream& Tcpjob::getStream() + { + return socket; + } + + int Tcpjob::getFd() const + { + return socket.getFd(); + } + + void Tcpjob::setRead() + { + socket.setTimeout(getSocketReadTimeout()); + } + + void Tcpjob::setWrite() + { + socket.setTimeout(getSocketWriteTimeout()); + } + +#ifdef USE_SSL + //////////////////////////////////////////////////////////////////////// + // SslTcpjob + // + void SslTcpjob::accept(const SslServer& listener) + { + log_debug("accept (ssl)"); + socket.accept(listener); + log_debug("connection accepted (ssl)"); + + struct sockaddr_storage s = socket.getSockAddr(); + struct sockaddr_storage sockaddr; + memcpy(&sockaddr, &s, sizeof(sockaddr)); + + getRequest().setPeerAddr(socket.getPeeraddr()); + getRequest().setServerAddr(sockaddr); + getRequest().setSsl(true); + + setRead(); + } + + std::iostream& SslTcpjob::getStream() + { + return socket; + } + + int SslTcpjob::getFd() const + { + return socket.getFd(); + } + + void SslTcpjob::setRead() + { + socket.setTimeout(getSocketReadTimeout()); + } + + void SslTcpjob::setWrite() + { + socket.setTimeout(getSocketWriteTimeout()); + } + +#endif // USE_SSL + +#ifdef USE_GNUTLS + //////////////////////////////////////////////////////////////////////// + // GnuTlsTcpjob + // + void GnuTlsTcpjob::accept(const GnuTlsServer& listener) + { + log_debug("accept (ssl)"); + socket.accept(listener); + log_debug("connection accepted (ssl)"); + + struct sockaddr_storage s = socket.getSockAddr(); + struct sockaddr_storage sockaddr; + memcpy(&sockaddr, &s, sizeof(sockaddr)); + + getRequest().setPeerAddr(socket.getPeeraddr()); + getRequest().setServerAddr(sockaddr); + getRequest().setSsl(true); + + setRead(); + } + + std::iostream& GnuTlsTcpjob::getStream() + { + return socket; + } + + int GnuTlsTcpjob::getFd() const + { + return socket.getFd(); + } + + void GnuTlsTcpjob::setRead() + { + socket.setTimeout(getSocketReadTimeout()); + } + + void GnuTlsTcpjob::setWrite() + { + socket.setTimeout(getSocketWriteTimeout()); + } + +#endif // USE_GNUTLS + + ////////////////////////////////////////////////////////////////////// + // Jobqueue + // + void Jobqueue::put(JobPtr j) + { + log_debug("Jobqueue::put"); + j->touch(); + + cxxtools::MutexLock lock(mutex); + + if (capacity > 0) + { + while (jobs.size() >= capacity) + { + log_warn("Jobqueue full"); + notFull.wait(lock); + } + } + + jobs.push_back(j); + + if (waitThreads == 0) + { + log_info("no waiting threads left"); + noWaitThreads.signal(); + } + + notEmpty.signal(); + } + + Jobqueue::JobPtr Jobqueue::get() + { + // wait, until a job is available + ++waitThreads; + + cxxtools::MutexLock lock(mutex); + while (jobs.empty()) + notEmpty.wait(lock); + + --waitThreads; + + log_debug("Jobqueue: fetch job " << waitThreads << " waiting threads left"); + + // take next job (queue is locked) + JobPtr j = jobs.front(); + jobs.pop_front(); + + // if there are more jobs, wake onther thread + if (!jobs.empty()) + notEmpty.signal(); + notFull.signal(); + + return j; + } + +} diff --git a/httpd/listener.cpp b/httpd/listener.cpp new file mode 100644 index 00000000..57c334d1 --- /dev/null +++ b/httpd/listener.cpp @@ -0,0 +1,167 @@ +/* listener.cpp + * Copyright (C) 2003 Tommi Maekitalo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and + * NON-INFRINGEMENT. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "tnt/listener.h" +#include "tnt/tntnet.h" +#include +#include +#include + +#ifdef WITH_GNUTLS +# include "tnt/gnutls.h" +#endif + +#ifdef WITH_OPENSSL +# include "tnt/openssl.h" +#endif + +log_define("tntnet.listener") + +static void doListenRetry(cxxtools::net::Server& server, + const char* ipaddr, unsigned short int port) +{ + for (unsigned n = 1; true; ++n) + { + try + { + log_debug("listen " << ipaddr << ':' << port); + server.listen(ipaddr, port, tnt::Listener::getBacklog()); + return; + } + catch (const cxxtools::net::Exception& e) + { + log_debug("cxxtools::net::Exception caught: errno=" << e.getErrno() << " msg=" << e.what()); + if (e.getErrno() != EADDRINUSE || n > tnt::Listener::getListenRetry()) + { + log_debug("rethrow exception"); + throw; + } + log_warn("address " << ipaddr << ':' << port << " in use - retry; n = " << n); + ::sleep(1); + } + } +} + +namespace tnt +{ + void ListenerBase::doStop() + { + log_warn("stop listener " << ipaddr << ':' << port); + try + { + // connect once to wake up listener, so it will check stop-flag + cxxtools::net::Stream(ipaddr, port); + } + catch (const std::exception& e) + { + log_warn("error waking up listener: " << e.what() << " try 127.0.0.1"); + cxxtools::net::Stream("127.0.0.1", port); + } + } + + int Listener::backlog = 16; + unsigned Listener::listenRetry = 5; + + Listener::Listener(const std::string& ipaddr, unsigned short int port, Jobqueue& q) + : ListenerBase(ipaddr, port), + queue(q) + { + log_info("listen ip=" << ipaddr << " port=" << port); + doListenRetry(server, ipaddr.c_str(), port); + } + + void Listener::run() + { + // accept-loop + log_debug("enter accept-loop"); + while (!Tntnet::shouldStop()) + { + try + { + Tcpjob* j = new Tcpjob; + Jobqueue::JobPtr p(j); + j->accept(server); + log_debug("connection accepted"); + + if (Tntnet::shouldStop()) + break; + + queue.put(p); + } + catch (const std::exception& e) + { + log_error("error in accept-loop: " << e.what()); + } + } + + log_debug("stop listener"); + } + +#ifdef WITH_GNUTLS +#define USE_SSL + +#endif + +#ifdef WITH_OPENSSL +#define USE_SSL + +#endif + +#ifdef USE_SSL + Ssllistener::Ssllistener(const char* certificateFile, + const char* keyFile, + const std::string& ipaddr, unsigned short int port, + Jobqueue& q) + : ListenerBase(ipaddr, port), + server(certificateFile, keyFile), + queue(q) + { + log_info("listen ip=" << ipaddr << " port=" << port << " (ssl)"); + doListenRetry(server, ipaddr.c_str(), port); + } + + void Ssllistener::run() + { + // accept-loop + log_debug("enter accept-loop (ssl)"); + while (!Tntnet::shouldStop()) + { + try + { + SslTcpjob* j = new SslTcpjob; + Jobqueue::JobPtr p(j); + j->accept(server); + + if (Tntnet::shouldStop()) + break; + + queue.put(p); + } + catch (const std::exception& e) + { + log_error("error in ssl-accept-loop: " << e.what()); + } + } + + log_debug("stop ssl-listener"); + } + +#endif // USE_SSL + +} diff --git a/httpd/poller.cpp b/httpd/poller.cpp new file mode 100644 index 00000000..c604f55f --- /dev/null +++ b/httpd/poller.cpp @@ -0,0 +1,190 @@ +/* poller.cpp + * Copyright (C) 2005 Tommi Maekitalo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and + * NON-INFRINGEMENT. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "tnt/poller.h" +#include "tnt/tntnet.h" +#include +#include +#include + +log_define("tntnet.poller") + +namespace tnt +{ + Poller::Poller(Jobqueue& q) + : queue(q), + poll_timeout(-1) + { + pipe(notify_pipe); + fcntl(notify_pipe[0], F_SETFL, O_NONBLOCK); + + pollfds.reserve(16); + pollfds[0].fd = notify_pipe[0]; + pollfds[0].events = POLLIN; + pollfds[0].revents = 0; + } + + void Poller::append_new_jobs() + { + cxxtools::MutexLock lock(mutex); + if (!new_jobs.empty()) + { + // append new jobs to current + log_debug("add " << new_jobs.size() << " new jobs to poll-list"); + + pollfds.reserve(current_jobs.size() + new_jobs.size() + 1); + + time_t currentTime; + time(¤tTime); + for (jobs_type::iterator it = new_jobs.begin(); + it != new_jobs.end(); ++it) + { + append(*it); + int msec; + if (poll_timeout < 0) + poll_timeout = (*it)->msecToTimeout(currentTime); + else if ((msec = (*it)->msecToTimeout(currentTime)) < poll_timeout) + poll_timeout = msec; + } + + new_jobs.clear(); + } + } + + void Poller::append(Jobqueue::JobPtr& job) + { + current_jobs.push_back(job); + + pollfd& p = *(pollfds.data() + current_jobs.size()); + p.fd = job->getFd(); + p.events = POLLIN; + } + + void Poller::run() + { + while (!Tntnet::shouldStop()) + { + append_new_jobs(); + + try + { + log_debug("poll timeout=" << poll_timeout); + ::poll(pollfds.data(), current_jobs.size() + 1, poll_timeout); + if (Tntnet::shouldStop()) + { + log_warn("stop poller"); + break; + } + + poll_timeout = -1; + + if (pollfds[0].revents != 0) + { + log_debug("read notify-pipe"); + char ch; + ::read(notify_pipe[0], &ch, 1); + pollfds[0].revents = 0; + } + + dispatch(); + } + catch (const std::exception& e) + { + log_error("error in poll-loop: " << e.what()); + } + } + } + + void Poller::doStop() + { + log_debug("notify stop"); + char ch = 'A'; + ::write(notify_pipe[1], &ch, 1); + } + + void Poller::dispatch() + { + log_debug("dispatch " << current_jobs.size() << " jobs"); + + time_t currentTime; + time(¤tTime); + for (unsigned i = 0; i < current_jobs.size(); ) + { + if (pollfds[i + 1].revents & POLLIN) + { + log_debug("job found " << pollfds[i + 1].fd); + + // put job into work-queue + queue.put(current_jobs[i]); + remove(i); + } + else if (pollfds[i + 1].revents != 0) + { + log_debug("pollevent " << std::hex << pollfds[i + 1].revents << " on fd " << pollfds[i + 1].fd); + remove(i); + } + else + { + // check timeout + int msec = current_jobs[i]->msecToTimeout(currentTime); + if (msec <= 0) + { + log_debug("keep-alive-timeout reached"); + remove(i); + } + else if (poll_timeout < 0 || msec < poll_timeout) + poll_timeout = msec; + + ++i; + } + } + } + + void Poller::remove(jobs_type::size_type n) + { + // replace job with last job in poller-list + jobs_type::size_type last = current_jobs.size() - 1; + + if (n != last) + { + pollfds[n + 1] = pollfds[last + 1]; + current_jobs[n] = current_jobs[last]; + } + + current_jobs.pop_back(); + } + + void Poller::addIdleJob(Jobqueue::JobPtr job) + { + log_debug("addIdleJob " << job->getFd()); + + { + cxxtools::MutexLock lock(mutex); + new_jobs.push_back(job); + } + + log_debug("notify " << job->getFd()); + + char ch = 'A'; + ::write(notify_pipe[1], &ch, 1); + + log_debug("addIdleJob ready"); + } + +} diff --git a/httpd/regex.cpp b/httpd/regex.cpp new file mode 100644 index 00000000..a9f52fc9 --- /dev/null +++ b/httpd/regex.cpp @@ -0,0 +1,175 @@ +/* regex.cpp + * Copyright (C) 2005 Tommi Maekitalo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and + * NON-INFRINGEMENT. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "tnt/regex.h" +#include +#include + +namespace tnt +{ + static inline bool isdigit(char ch) + { + return ch >= '0' && ch <= '9'; + } + + unsigned regex_smatch::size() const + { + unsigned n; + for (n = 0; n < 10 && matchbuf[n].rm_so >= 0; ++n) + ; + + return n; + } + + std::string regex_smatch::get(unsigned n) const + { + return str.substr(matchbuf[n].rm_so, matchbuf[n].rm_eo - matchbuf[n].rm_so); + } + + std::string regex_smatch::format(const std::string& s) const + { + enum state_type + { + state_0, + state_esc, + state_var0, + state_var1, + state_1 + } state; + + state = state_0; + std::string ret; + + for (std::string::const_iterator it = s.begin(); it != s.end(); ++it) + { + char ch = *it; + + switch (state) + { + case state_0: + if (ch == '$') + state = state_var0; + else if (ch == '\\') + { + ret = std::string(s.begin(), it); + state = state_esc; + } + break; + + case state_esc: + ret += ch; + state = state_1; + break; + + case state_var0: + if (isdigit(ch)) + { + ret = std::string(s.begin(), it - 1); + regoff_t s = matchbuf[ch - '0'].rm_so; + regoff_t e = matchbuf[ch - '0'].rm_eo; + if (s >= 0 && e >= 0) + ret.append(str, s, e-s); + state = state_1; + } + else + state = state_0; + break; + + case state_1: + if (ch == '$') + state = state_var1; + else if (state == '\\') + state = state_esc; + else + ret += ch; + break; + + case state_var1: + if (isdigit(ch)) + { + unsigned s = matchbuf[ch - '0'].rm_so; + unsigned e = matchbuf[ch - '0'].rm_eo; + if (s >= 0 && e >= 0) + ret.append(str, s, e-s); + state = state_1; + } + else if (ch == '$') + ret += '$'; + else + { + ret += '$'; + ret += ch; + } + break; + } + } + + switch (state) + { + case state_0: + case state_var0: + return s; + + case state_esc: + return ret + '\\'; + + case state_var1: + return ret + '$'; + + case state_1: + return ret; + } + + return ret; + } + + void regex::checkerr(int ret) const + { + if (ret != 0) + { + char errbuf[256]; + regerror(ret, &expr, errbuf, sizeof(errbuf)); + throw std::runtime_error(errbuf); + } + } + + bool regex::match(const std::string& str_, int eflags) const + { + regex_smatch smatch; + return match(str_, smatch, eflags); + } + + bool regex::match(const std::string& str_, regex_smatch& smatch, int eflags) const + { + smatch.str = str_; + int ret = regexec(&expr, str_.c_str(), + sizeof(smatch.matchbuf) / sizeof(regmatch_t), smatch.matchbuf, eflags); + + if (ret ==REG_NOMATCH) + return false; + + checkerr(ret); + return true; + } + + void regex::free() + { + regfree(&expr); + } +} diff --git a/httpd/tnt/dispatcher.h b/httpd/tnt/dispatcher.h new file mode 100644 index 00000000..218efcbf --- /dev/null +++ b/httpd/tnt/dispatcher.h @@ -0,0 +1,121 @@ +/* tnt/dispatcher.h + * Copyright (C) 2003-2005 Tommi Maekitalo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and + * NON-INFRINGEMENT. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef TNT_DISPATCHER_H +#define TNT_DISPATCHER_H + +#include +#include +#include +#include +#include "tnt/regex.h" + +namespace tnt +{ + // Dispatcher - one per host + class Dispatcher : public Urlmapper + { + public: + class CompidentType : public Compident + { + public: + typedef std::vector args_type; + + private: + std::string pathinfo; + args_type args; + bool pathinfo_set; + + public: + CompidentType() + : pathinfo_set(false) + { } + + explicit CompidentType(const std::string& ident) + : Compident(ident), + pathinfo_set(false) + { } + + bool hasPathInfo() const + { return pathinfo_set; } + void setPathInfo(const std::string& p) + { pathinfo = p; pathinfo_set = true; } + void setArgs(const args_type& a) + { args = a; } + const std::string& getPathInfo() const + { return pathinfo; } + const args_type& getArgs() const + { return args; } + args_type& getArgsRef() + { return args; } + }; + + private: + typedef std::vector > urlmap_type; + urlmap_type urlmap; // map url to soname/compname + mutable cxxtools::RWLock rwlock; + + typedef std::map, + CompidentType> urlMapCacheType; + mutable urlMapCacheType urlMapCache; + static urlMapCacheType::size_type maxUrlMapCache; + + // don't make this public - it's not threadsafe: + CompidentType mapCompNext(const std::string& compUrl, + urlmap_type::const_iterator& pos) const; + + public: + virtual ~Dispatcher() { } + + void addUrlMapEntry(const std::string& url, const CompidentType& ci); + + Compident mapComp(const std::string& compUrl) const; + + static urlMapCacheType::size_type getMaxUrlMapCache() + { return maxUrlMapCache; } + static void setMaxUrlMapCache(urlMapCacheType::size_type s) + { maxUrlMapCache = s; } + + friend class PosType; + + class PosType + { + const Dispatcher& dis; + cxxtools::RdLock lock; + urlmap_type::const_iterator pos; + std::string url; + bool first; + + public: + PosType(const Dispatcher& d, const std::string& u) + : dis(d), + lock(dis.rwlock), + pos(dis.urlmap.begin()), + url(u), + first(true) + { } + + CompidentType getNext(); + }; + }; + +} + +#endif // TNT_DISPATCHER_H + diff --git a/httpd/tnt/gcryptinit.h b/httpd/tnt/gcryptinit.h new file mode 100644 index 00000000..3b6ee335 --- /dev/null +++ b/httpd/tnt/gcryptinit.h @@ -0,0 +1,37 @@ +/* tnt/gcryptinit.h + * Copyright (C) 2003-2005 Tommi Maekitalo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and + * NON-INFRINGEMENT. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef TNT_GCRYPTINIT_H +#define TNT_GCRYPTINIT_H + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +gcry_error_t gcrypt_init(); + +#ifdef __cplusplus +} +#endif + +#endif // TNT_GCRYPTINIT_H + diff --git a/httpd/tnt/gnutls.h b/httpd/tnt/gnutls.h new file mode 100644 index 00000000..8d8811c8 --- /dev/null +++ b/httpd/tnt/gnutls.h @@ -0,0 +1,146 @@ +/* tnt/gnutls.h + * Copyright (C) 2006 Tommi Maekitalo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and + * NON-INFRINGEMENT. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef TNT_GNUTLS_H +#define TNT_GNUTLS_H + +#include +#include + +namespace tnt +{ + class GnuTlsException : public std::runtime_error + { + unsigned long code; + static std::string formatMessage(const std::string& function, int code_); + + public: + GnuTlsException(const std::string& function, int code_) + : std::runtime_error(formatMessage(function, code_)), + code(code_) + { } + + unsigned long getCode() const + { return code; } + }; + + class GnuTlsInit + { + static unsigned initCount; + static gnutls_dh_params dhParams; + + public: + GnuTlsInit(); + ~GnuTlsInit(); + + gnutls_dh_params getDhParams() const { return dhParams; } + }; + + class GnuTlsX509Cred + { + gnutls_certificate_credentials x509_cred; + GnuTlsInit init; + + public: + GnuTlsX509Cred(const char* certificateFile, const char* privateKeyFile); + ~GnuTlsX509Cred(); + + operator gnutls_certificate_credentials() const { return x509_cred; } + }; + + class GnuTlsServer : public cxxtools::net::Server + { + GnuTlsX509Cred cred; + + public: + GnuTlsServer(const char* certificateFile, const char* privateKeyFile); + + gnutls_certificate_credentials getCred() const { return cred; } + }; + + class GnuTlsStream : public cxxtools::net::Stream + { + gnutls_session session; + + public: + GnuTlsStream() + : session(0) + { } + + explicit GnuTlsStream(int fd) + : cxxtools::net::Stream(fd) + { } + + explicit GnuTlsStream(const GnuTlsServer& server) + { accept(server); } + + ~GnuTlsStream(); + + void accept(const GnuTlsServer& server); + int sslRead(char* buffer, int bufsize) const; + int sslWrite(const char* buffer, int bufsize) const; + void shutdown() const; + }; + + class GnuTls_streambuf : public std::streambuf + { + GnuTlsStream& m_stream; + char_type* m_buffer; + unsigned m_bufsize; + + public: + explicit GnuTls_streambuf(GnuTlsStream& stream, unsigned bufsize = 256, int timeout = -1); + ~GnuTls_streambuf() + { delete[] m_buffer; } + + void setTimeout(int t) { m_stream.setTimeout(t); } + int getTimeout() const { return m_stream.getTimeout(); } + + /// overload std::streambuf + int_type overflow(int_type c); + /// overload std::streambuf + int_type underflow(); + /// overload std::streambuf + int sync(); + }; + + class GnuTls_iostream : public GnuTlsStream, public std::iostream + { + GnuTls_streambuf m_buffer; + + public: + explicit GnuTls_iostream(unsigned bufsize = 256, int timeout = -1) + : GnuTlsStream(-1), + std::iostream(&m_buffer), + m_buffer(*this, bufsize, timeout) + { } + + explicit GnuTls_iostream(const GnuTlsServer& server, unsigned bufsize = 256, int timeout = -1) + : GnuTlsStream(server), + std::iostream(&m_buffer), + m_buffer(*this, bufsize, timeout) + { } + + void setTimeout(int timeout) { m_buffer.setTimeout(timeout); } + int getTimeout() const { return m_buffer.getTimeout(); } + }; +} + +#endif // TNT_GNUTLS_H + diff --git a/httpd/tnt/job.h b/httpd/tnt/job.h new file mode 100644 index 00000000..5e839fd5 --- /dev/null +++ b/httpd/tnt/job.h @@ -0,0 +1,201 @@ +/* tnt/job.h + * Copyright (C) 2003-2005 Tommi Maekitalo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and + * NON-INFRINGEMENT. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef TNT_JOB_H +#define TNT_JOB_H + +#ifdef HAVE_CONFIG_H +# include +#endif +#include +#include +#include +#include +#include +#include +#include +#include "tnt/ssl.h" + +/** +// in tntnet (mainthread): +Jobqueue queue; +void mainloop() +{ + while (1) + { + Jobqueue::JobPtr j = new Tcpjob(); + j->accept(poller.get()); + queue.put(j); + } +} + +// in server (workerthread): +void Server::run() +{ + while (1) + { + Jobqueue::JobPtr j = queue.get(); + std::iostream& socket = j->getStream(); + processRequest(socket); + } +} +*/ + +namespace tnt +{ + /** Job - one per request */ + class Job + { + unsigned keepAliveCounter; + + HttpRequest request; + HttpMessage::Parser parser; + time_t lastAccessTime; + + unsigned refs; + + static unsigned socket_read_timeout; + static unsigned socket_write_timeout; + static unsigned keepalive_max; + static unsigned socket_buffer_size; + + public: + Job() + : keepAliveCounter(keepalive_max), + parser(request), + lastAccessTime(0), + refs(0) + { } + + protected: + virtual ~Job(); + + public: + unsigned addRef() { return ++refs; } + unsigned release() + { + if (--refs == 0) + { + delete this; + return 0; + } + else + return refs; + } + + virtual std::iostream& getStream() = 0; + virtual int getFd() const = 0; + virtual void setRead() = 0; + virtual void setWrite() = 0; + + HttpRequest& getRequest() { return request; } + HttpMessage::Parser& getParser() { return parser; } + + unsigned decrementKeepAliveCounter() + { return keepAliveCounter > 0 ? --keepAliveCounter : 0; } + void clear(); + void touch() { time(&lastAccessTime); } + int msecToTimeout(time_t currentTime) const; + + static void setSocketReadTimeout(unsigned ms) { socket_read_timeout = ms; } + static void setSocketWriteTimeout(unsigned ms) { socket_write_timeout = ms; } + static void setKeepAliveMax(unsigned n) { keepalive_max = n; } + static void setSocketBufferSize(unsigned b) { socket_buffer_size = b; } + + static unsigned getSocketReadTimeout() { return socket_read_timeout; } + static unsigned getSocketWriteTimeout() { return socket_write_timeout; } + static unsigned getKeepAliveTimeout(); + static unsigned getKeepAliveMax() { return keepalive_max; } + static unsigned getSocketBufferSize() { return socket_buffer_size; } + }; + + class Tcpjob : public Job + { + cxxtools::net::iostream socket; + + public: + Tcpjob() + : socket(getSocketBufferSize(), getSocketReadTimeout()) + { } + + void accept(const cxxtools::net::Server& listener); + + std::iostream& getStream(); + int getFd() const; + void setRead(); + void setWrite(); + }; + +#ifdef USE_SSL + class SslTcpjob : public Job + { + ssl_iostream socket; + + public: + SslTcpjob() + : socket(getSocketBufferSize(), getSocketReadTimeout()) + { } + + void accept(const SslServer& listener); + + std::iostream& getStream(); + int getFd() const; + void setRead(); + void setWrite(); + }; +#endif // USE_SSL + + /** Jobqueue - one per process */ + class Jobqueue + { + public: + typedef Pointer JobPtr; + + cxxtools::Condition noWaitThreads; + + private: + std::deque jobs; + cxxtools::Mutex mutex; + cxxtools::Condition notEmpty; + cxxtools::Condition notFull; + unsigned waitThreads; + unsigned capacity; + + public: + explicit Jobqueue(unsigned capacity_) + : waitThreads(0), + capacity(capacity_) + { } + + void put(JobPtr j); + JobPtr get(); + + void setCapacity(unsigned c) + { capacity = c; } + unsigned getCapacity() const + { return capacity; } + unsigned getWaitThreadCount() const + { return waitThreads; } + bool empty() const + { return jobs.empty(); } + }; +} + +#endif // TNT_JOB_H + diff --git a/httpd/tnt/listener.h b/httpd/tnt/listener.h new file mode 100644 index 00000000..49917638 --- /dev/null +++ b/httpd/tnt/listener.h @@ -0,0 +1,78 @@ +/* tnt/listener.h + * Copyright (C) 2003 Tommi Maekitalo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and + * NON-INFRINGEMENT. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef TNT_LISTENER_H +#define TNT_LISTENER_H + +#ifdef HAVE_CONFIG_H +# include +#endif +#include +#include "tnt/job.h" + +namespace tnt +{ + class ListenerBase : public cxxtools::AttachedThread + { + std::string ipaddr; + unsigned short int port; + + public: + ListenerBase(const std::string& ipaddr_, unsigned short int port_) + : ipaddr(ipaddr_), + port(port_) + { } + + void doStop(); + }; + + class Listener : public ListenerBase + { + cxxtools::net::Server server; + Jobqueue& queue; + static int backlog; + static unsigned listenRetry; + + public: + Listener(const std::string& ipaddr, unsigned short int port, Jobqueue& q); + virtual void run(); + + static void setBacklog(int backlog_) { backlog = backlog_; } + static int getBacklog() { return backlog; } + static void setListenRetry(unsigned listenRetry_) { listenRetry = listenRetry_; } + static unsigned getListenRetry() { return listenRetry; } + }; + +#ifdef USE_SSL + class Ssllistener : public ListenerBase + { + SslServer server; + Jobqueue& queue; + + public: + Ssllistener(const char* certificateFile, const char* keyFile, + const std::string& ipaddr, unsigned short int port, Jobqueue& q); + virtual void run(); + }; +#endif // USE_SSL + +} + +#endif // TNT_LISTENER_H + diff --git a/httpd/tnt/openssl.h b/httpd/tnt/openssl.h new file mode 100644 index 00000000..863782f2 --- /dev/null +++ b/httpd/tnt/openssl.h @@ -0,0 +1,121 @@ +/* tnt/openssl.h + * Copyright (C) 2003 Tommi Maekitalo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and + * NON-INFRINGEMENT. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef TNT_OPENSSL_H +#define TNT_OPENSSL_H + +#include +#include + +namespace tnt +{ + class OpensslException : public std::runtime_error + { + unsigned long code; + + public: + OpensslException(const std::string& what, unsigned long code_) + : std::runtime_error(what), + code(code_) + { } + + unsigned long getCode() const + { return code; } + }; + + class OpensslServer : public cxxtools::net::Server + { + SSL_CTX* ctx; + void installCertificates(const char* certificateFile, const char* privateKeyFile); + + public: + OpensslServer(const char* certificateFile); + OpensslServer(const char* certificateFile, const char* privateKeyFile); + ~OpensslServer(); + + SSL_CTX* getSslContext() const { return ctx; } + }; + + class OpensslStream : public cxxtools::net::Stream + { + SSL* ssl; + + public: + OpensslStream(); + + explicit OpensslStream(int fd) + : cxxtools::net::Stream(fd) + { } + + explicit OpensslStream(const OpensslServer& server); + ~OpensslStream(); + + void accept(const OpensslServer& server); + + int sslRead(char* buffer, int bufsize) const; + int sslWrite(const char* buffer, int bufsize) const; + void shutdown() const; + }; + + class openssl_streambuf : public std::streambuf + { + OpensslStream& m_stream; + char_type* m_buffer; + unsigned m_bufsize; + + public: + explicit openssl_streambuf(OpensslStream& stream, unsigned bufsize = 256, int timeout = -1); + ~openssl_streambuf() + { delete[] m_buffer; } + + void setTimeout(int t) { m_stream.setTimeout(t); } + int getTimeout() const { return m_stream.getTimeout(); } + + /// overload std::streambuf + int_type overflow(int_type c); + /// overload std::streambuf + int_type underflow(); + /// overload std::streambuf + int sync(); + }; + + class openssl_iostream : public OpensslStream, public std::iostream + { + openssl_streambuf m_buffer; + + public: + explicit openssl_iostream(unsigned bufsize = 256, int timeout = -1) + : OpensslStream(-1), + std::iostream(&m_buffer), + m_buffer(*this, bufsize, timeout) + { } + + explicit openssl_iostream(const OpensslServer& server, unsigned bufsize = 256, int timeout = -1) + : OpensslStream(server), + std::iostream(&m_buffer), + m_buffer(*this, bufsize, timeout) + { } + + void setTimeout(int timeout) { m_buffer.setTimeout(timeout); } + int getTimeout() const { return m_buffer.getTimeout(); } + }; +} + +#endif // TNT_OPENSSL_H + diff --git a/httpd/tnt/poller.h b/httpd/tnt/poller.h new file mode 100644 index 00000000..d9b12e9f --- /dev/null +++ b/httpd/tnt/poller.h @@ -0,0 +1,61 @@ +/* tnt/poller.h + * Copyright (C) 2005 Tommi Maekitalo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and + * NON-INFRINGEMENT. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef TNT_POLLER_H +#define TNT_POLLER_H + +#include "tnt/job.h" +#include +#include +#include + +namespace tnt +{ + class Poller : public cxxtools::AttachedThread + { + Jobqueue& queue; + int notify_pipe[2]; + + typedef std::deque jobs_type; + + jobs_type current_jobs; + cxxtools::Dynbuffer pollfds; + + jobs_type new_jobs; + + cxxtools::Mutex mutex; + int poll_timeout; + + void append_new_jobs(); + void append(Jobqueue::JobPtr& job); + void dispatch(); + void remove(jobs_type::size_type n); + + public: + Poller(Jobqueue& q); + + virtual void run(); + void doStop(); + void addIdleJob(Jobqueue::JobPtr job); + }; + +} + +#endif // TNT_POLLER_H + diff --git a/httpd/tnt/regex.h b/httpd/tnt/regex.h new file mode 100644 index 00000000..b2a30e8e --- /dev/null +++ b/httpd/tnt/regex.h @@ -0,0 +1,79 @@ +/* tnt/regex.h + * Copyright (C) 2005 Tommi Maekitalo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and + * NON-INFRINGEMENT. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef TNT_REGEX_H +#define TNT_REGEX_H + +#include +#include +#include + +namespace tnt +{ + /// collects matches in a regex + class regex_smatch + { + friend class regex; + + std::string str; + regmatch_t matchbuf[10]; + + public: + unsigned size() const; + std::string get(unsigned n) const; + std::string format(const std::string& s) const; + }; + + /// regex(3)-wrapper. + /// Warning: incomplete, but sufficient for tntnet. + /// Regular expression is not automatically freed. Tntnet needs to + /// put regex into a stl-container, so it needs to be copyable. + /// For this class to be complete, the regex_t needs to be + /// reference-counted. This is unneeded for tntnet, because the regex is + /// never freed anyway. + class regex + { + regex_t expr; + + void checkerr(int ret) const; + + public: + explicit regex(const char* ex, int cflags = REG_EXTENDED) + { + checkerr(::regcomp(&expr, ex, cflags)); + } + + explicit regex(const std::string& ex, int cflags = REG_EXTENDED) + { + checkerr(::regcomp(&expr, ex.c_str(), cflags)); + } + + bool match(const std::string& str_, regex_smatch& smatch, int eflags = 0) const; + bool match(const std::string& str_, int eflags = 0) const; + + void free(); + + private: + friend class value_type; + }; + +} + +#endif // TNT_REGEX_H + diff --git a/httpd/tnt/ssl.h b/httpd/tnt/ssl.h new file mode 100644 index 00000000..6660d2f8 --- /dev/null +++ b/httpd/tnt/ssl.h @@ -0,0 +1,52 @@ +/* tnt/ssl.h + * Copyright (C) 2006 Tommi Maekitalo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and + * NON-INFRINGEMENT. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef TNT_SSL_H +#define TNT_SSL_H + +#ifdef WITH_GNUTLS +# include "tnt/gnutls.h" +# define USE_SSL +#endif + +#ifdef WITH_OPENSSL +# include "tnt/openssl.h" +# define USE_SSL +#endif + +namespace tnt +{ +#ifdef WITH_GNUTLS + + typedef GnuTlsServer SslServer; + typedef GnuTls_iostream ssl_iostream; + +#endif + +#ifdef WITH_OPENSSL + + typedef OpensslServer SslServer; + typedef openssl_iostream ssl_iostream; + +#endif + +} + +#endif // TNT_SSL_H + diff --git a/httpd/tnt/tntnet.h b/httpd/tnt/tntnet.h new file mode 100644 index 00000000..a83784e5 --- /dev/null +++ b/httpd/tnt/tntnet.h @@ -0,0 +1,99 @@ +/* tnt/tntnet.h + * Copyright (C) 2003-2005 Tommi Maekitalo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and + * NON-INFRINGEMENT. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef TNT_TNTNET_H +#define TNT_TNTNET_H + +#include +#include "tnt/tntconfig.h" +#include "tnt/job.h" +#include "tnt/poller.h" +#include "tnt/dispatcher.h" +#include +#include + +namespace tnt +{ + class ListenerBase; + + class Tntnet + { + std::string configFile; + Tntconfig config; + cxxtools::Arg propertyfilename; + cxxtools::Arg debug; + bool isDaemon; + + unsigned minthreads; + unsigned maxthreads; + unsigned long threadstartdelay; + + Jobqueue queue; + + static bool stop; + static int ret; + typedef std::set listeners_type; + listeners_type listeners; + + Poller pollerthread; + Dispatcher d_dispatcher; + + static std::string pidFileName; + + ScopeManager scopemanager; + + // helper methods + void setUser() const; + void setGroup() const; + void setDir(const char* def) const; + int mkDaemon() const; // returns pipe + void closeStdHandles() const; + + // noncopyable + Tntnet(const Tntnet&); + Tntnet& operator= (const Tntnet&); + + void initLogging(); + void writePidfile(int pid); + void monitorProcess(int workerPid); + void initWorkerProcess(); + void workerProcess(int filedes = -1); + + void timerTask(); + void loadConfiguration(); + + public: + Tntnet(int& argc, char* argv[]); + int run(); + + static void shutdown(); + static void restart(); + static bool shouldStop() { return stop; } + + Jobqueue& getQueue() { return queue; } + Poller& getPoller() { return pollerthread; } + const Dispatcher& getDispatcher() const { return d_dispatcher; } + const Tntconfig& getConfig() const { return config; } + ScopeManager& getScopemanager() { return scopemanager; } + }; + +} + +#endif // TNT_TNTNET_H + diff --git a/httpd/tnt/worker.h b/httpd/tnt/worker.h new file mode 100644 index 00000000..be42841b --- /dev/null +++ b/httpd/tnt/worker.h @@ -0,0 +1,91 @@ +/* tnt/worker.h + * Copyright (C) 2003-2005 Tommi Maekitalo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and + * NON-INFRINGEMENT. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef TNT_WORKER_H +#define TNT_WORKER_H + +#include +#include +#include +#include +#include +#include +#include + +namespace tnt +{ + class HttpRequest; + class HttpReply; + + class Worker : public cxxtools::DetachedThread + { + static cxxtools::Mutex mutex; + static unsigned nextThreadNumber; + + Tntnet& application; + + typedef cxxtools::Pool ComploaderPoolType; + static ComploaderPoolType comploaderPool; + + ComploaderPoolType::objectptr_type comploaderObject; + Comploader& comploader; + + Scope threadScope; + + pthread_t threadId; + const char* state; + time_t lastWaitTime; + + typedef std::set workers_type; + static workers_type workers; + + static unsigned maxRequestTime; + static unsigned minThreads; + static bool enableCompression; + + bool processRequest(HttpRequest& request, std::iostream& socket, + unsigned keepAliveCount); + void healthCheck(time_t currentTime); + + ~Worker(); + + public: + Worker(Tntnet& app); + + virtual void run(); + + void dispatch(HttpRequest& request, HttpReply& reply); + + static void timer(); + + /// Sets a hard limit for request-time. + /// When the time is exceeded, this process exits. + static void setMaxRequestTime(unsigned sec) { maxRequestTime = sec; } + static unsigned getMaxRequestTime() { return maxRequestTime; } + + static workers_type::size_type getCountThreads(); + static void setMinThreads(unsigned n) { minThreads = n; } + + static void setEnableCompression(bool sw = true) { enableCompression = sw; } + static unsigned getEnableCompression() { return enableCompression; } + }; +} + +#endif // TNT_WORKER_H + diff --git a/httpd/tntnet.cpp b/httpd/tntnet.cpp new file mode 100644 index 00000000..ebaad625 --- /dev/null +++ b/httpd/tntnet.cpp @@ -0,0 +1,832 @@ +/* tntnet.cpp + * Copyright (C) 2003-2005 Tommi Maekitalo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and + * NON-INFRINGEMENT. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "tnt/worker.h" +#include "tnt/tntnet.h" +#include "tnt/listener.h" +#include "tnt/http.h" +#include "tnt/httpreply.h" +#include "tnt/sessionscope.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_CONFIG_H +# include +#endif + +#ifndef TNTNET_CONF +# define TNTNET_CONF "/etc/tntnet.conf" +#endif + +#ifndef TNTNET_PID +# define TNTNET_PID "/var/run/tntnet.pid" +#endif + +log_define("tntnet.tntnet") + +#define log_error_master(expr) \ + do { \ + std::cout << "ERROR: " << expr << std::endl; \ + } while(false) + +#define log_warn_master(expr) \ + do { \ + std::cout << "WARN: " << expr << std::endl; \ + } while(false) + +#define log_info_master(expr) \ + do { \ + std::cout << "INFO: " << expr << std::endl; \ + } while(false) + +#define log_debug_master(expr) \ + do { \ + std::cout << "DEBUG: " << expr << std::endl; \ + } while(false) + +namespace +{ + void sigEnd(int) + { + tnt::Tntnet::shutdown(); + } + + void sigReload(int) + { + // stopping child with 111 signals monitor-process to restart child + tnt::Tntnet::restart(); + } + + void configureDispatcher(tnt::Dispatcher& dis, const tnt::Tntconfig& config) + { + typedef tnt::Dispatcher::CompidentType CompidentType; + + const tnt::Tntconfig::config_entries_type& params = config.getConfigValues(); + + tnt::Tntconfig::config_entries_type::const_iterator vi; + for (vi = params.begin(); vi != params.end(); ++vi) + { + const tnt::Tntconfig::config_entry_type& v = *vi; + const tnt::Tntconfig::params_type& args = v.params; + if (v.key == "MapUrl") + { + if (args.size() < 2) + { + std::ostringstream msg; + msg << "invalid number of parameters (" << args.size() << ") in MapUrl"; + throw std::runtime_error(msg.str()); + } + + std::string url = args[0]; + + CompidentType ci = CompidentType(args[1]); + if (args.size() > 2) + { + ci.setPathInfo(args[2]); + if (args.size() > 3) + ci.setArgs(CompidentType::args_type(args.begin() + 3, args.end())); + } + + dis.addUrlMapEntry(url, ci); + } + } + } + + bool checkChildSuccess(int fd) + { + log_debug("checkChildSuccess"); + + char buffer; + int ret = ::read(fd, &buffer, 1); + if (ret < 0) + throw std::runtime_error( + std::string("error in read: ") + strerror(errno)); + close(fd); + return ret > 0; + } + + void signalParentSuccess(int fd) + { + log_debug("signalParentSuccess"); + + ssize_t s = write(fd, "1", 1); + if (s < 0) + throw std::runtime_error( + std::string("error in write(): ") + strerror(errno)); + close(fd); + } + +} + +namespace tnt +{ + //////////////////////////////////////////////////////////////////////// + // Tntnet + // + bool Tntnet::stop = false; + int Tntnet::ret = 0; + std::string Tntnet::pidFileName; + + Tntnet::Tntnet(int& argc, char* argv[]) + : propertyfilename(argc, argv, 'P'), + debug(argc, argv, 'd'), + queue(1000), + pollerthread(queue) + { + // check for argument -c + cxxtools::Arg conf(argc, argv, 'c'); + if (conf.isSet()) + configFile = conf; + else + { + // read 1st parameter from argument-list + cxxtools::Arg conf(argc, argv); + if (conf.isSet()) + configFile = conf; + else + { + // check environment-variable TNTNET_CONF + const char* tntnetConf = ::getenv("TNTNET_CONF"); + if (tntnetConf) + configFile = tntnetConf; + else + configFile = TNTNET_CONF; // take default + } + } + } + + void Tntnet::setGroup() const + { + Tntconfig::params_type group = config.getConfigValue("Group"); + if (group.size() >= 1) + { + struct group * gr = getgrnam(group.begin()->c_str()); + if (gr == 0) + throw std::runtime_error("unknown group " + *group.begin()); + + log_debug("change group to " << *group.begin() << '(' << gr->gr_gid << ')'); + + int ret = setgid(gr->gr_gid); + if (ret != 0) + { + std::ostringstream msg; + msg << "cannot change group to " << *group.begin() + << '(' << gr->gr_gid << "): " << strerror(errno); + throw std::runtime_error(msg.str()); + } + } + } + + void Tntnet::setDir(const char* def) const + { + std::string dir = config.getValue("Dir", def); + + if (!dir.empty()) + { + log_debug("chdir(" << dir << ')'); + if (chdir(dir.c_str()) == -1) + { + throw std::runtime_error( + std::string("error in chdir(): ") + + strerror(errno)); + } + } + + std::string chrootdir = config.getValue("Chroot"); + if (!chrootdir.empty() && chroot(chrootdir.c_str()) == -1) + throw std::runtime_error( + std::string("error in chroot(): ") + + strerror(errno)); + } + + void Tntnet::setUser() const + { + Tntconfig::params_type user = config.getConfigValue("User"); + if (user.size() >= 1) + { + struct passwd * pw = getpwnam(user.begin()->c_str()); + if (pw == 0) + throw std::runtime_error("unknown user " + *user.begin()); + + log_debug("change user to " << *user.begin() << '(' << pw->pw_uid << ')'); + + int ret = setuid(pw->pw_uid); + if (ret != 0) + { + std::ostringstream msg; + msg << "cannot change user to " << *user.begin() + << '(' << pw->pw_uid << "): " << strerror(errno); + throw std::runtime_error(msg.str()); + } + } + } + + int Tntnet::mkDaemon() const + { + log_info("start daemon-mode"); + + int filedes[2]; + + if (pipe(filedes) != 0) + throw std::runtime_error( + std::string("error in pipe(int[2]): ") + strerror(errno)); + + int pid = fork(); + if (pid > 0) + { + // parent + + close(filedes[1]); // close write-fd + + // exit with error, when nothing read + ::exit (checkChildSuccess(filedes[0]) ? 0 : 1); + } + else if (pid < 0) + throw std::runtime_error( + std::string("error in fork(): ") + strerror(errno)); + + // child + + close(filedes[0]); // close read-fd + + // setsid + if (setsid() == -1) + throw std::runtime_error( + std::string("error in setsid(): ") + + strerror(errno)); + + // return write-fd + return filedes[1]; + } + + void Tntnet::closeStdHandles() const + { + // close stdin, stdout and stderr + bool noclosestd = config.getBoolValue("NoCloseStdout", false); + if (noclosestd) + { + log_debug("not closing stdout"); + return; + } + + if (freopen("/dev/null", "r", stdin) == 0) + throw std::runtime_error( + std::string("unable to replace stdin with /dev/null: ") + + strerror(errno)); + + if (freopen("/dev/null", "w", stdout) == 0) + throw std::runtime_error( + std::string("unable to replace stdout with /dev/null: ") + + strerror(errno)); + + if (freopen("/dev/null", "w", stderr) == 0) + throw std::runtime_error( + std::string("unable to replace stderr with /dev/null: ") + + strerror(errno)); + } + + int Tntnet::run() + { + loadConfiguration(); + + if (debug) + { + log_init_debug(); + log_warn("Debugmode"); + isDaemon = false; + } + else + { + isDaemon = config.getBoolValue("Daemon", false); + } + + if (isDaemon) + { + int filedes = mkDaemon(); + + setDir(""); + + bool nomonitor = config.getBoolValue("NoMonitor", false); + if (nomonitor) + { + log_debug("start worker-process without monitor"); + writePidfile(getpid()); + initWorkerProcess(); + + // change group and user + setGroup(); + setUser(); + + initLogging(); + workerProcess(filedes); + } + else + { + initWorkerProcess(); + do + { + int filedes_monitor[2]; + + if (pipe(filedes_monitor) != 0) + throw std::runtime_error( + std::string("error in pipe(int[2]): ") + strerror(errno)); + + // fork workerprocess + int pid = fork(); + if (pid < 0) + throw std::runtime_error( + std::string("error in forking workerprocess: ") + + strerror(errno)); + + if (pid == 0) + { + // workerprocess + + close(filedes_monitor[0]); // close read-fd + + // change group and user + setGroup(); + setUser(); + + initLogging(); + workerProcess(filedes_monitor[1]); + return ret; + } + else + { + close(filedes_monitor[1]); // close write-fd + + // write child-pid + writePidfile(pid); + + // wait for worker to signal success + if (!checkChildSuccess(filedes_monitor[0])) + ::exit(1); + if (filedes >= 0) + { + signalParentSuccess(filedes); + filedes = -1; + } + + monitorProcess(pid); + if (!stop) + sleep(1); + } + + } while (!stop); + } + } + else + { + log_info("no daemon-mode"); + initLogging(); + initWorkerProcess(); + workerProcess(); + } + + return 0; + } + + void Tntnet::initLogging() + { + if (debug) + return; // logging already initialized + + std::string pf; + if (propertyfilename.isSet()) + pf = propertyfilename.getValue(); + else + pf = config.getValue("PropertyFile"); + + if (pf.empty()) + log_init(); + else + { + struct stat properties_stat; + if (stat(pf.c_str(), &properties_stat) != 0) + throw std::runtime_error("propertyfile " + pf + " not found"); + + log_init(pf.c_str()); + } + } + + void Tntnet::writePidfile(int pid) + { + pidFileName = config.getValue("PidFile", TNTNET_PID); + + log_debug("pidfile=" << pidFileName); + + if (!pidFileName.empty()) + { + if (pidFileName[0] != '/') + { + // prepend current working-directory to pidfilename if not absolute + std::vector buf(256); + const char* cwd; + while (true) + { + cwd = ::getcwd(&buf[0], buf.size()); + if (cwd) + break; + else if (errno == ERANGE) + buf.resize(buf.size() * 2); + else + throw std::runtime_error( + std::string("error in getcwd: ") + strerror(errno)); + } + pidFileName = std::string(cwd) + '/' + pidFileName; + log_debug("pidfile=" << pidFileName); + } + + std::ofstream pidfile(pidFileName.c_str()); + if (!pidfile) + throw std::runtime_error("unable to open pid-file " + pidFileName); + pidfile << pid; + } + } + + void Tntnet::monitorProcess(int workerPid) + { + setDir(""); + + // close stdin, stdout and stderr + closeStdHandles(); + + int status; + waitpid(workerPid, &status, 0); + + if (WIFSIGNALED(status)) + { + // SIGTERM means normal exit + if (WTERMSIG(status) == SIGTERM) + { + log_info_master("child terminated normally"); + stop = true; + } + else + { + log_warn_master("child terminated with signal " + << WTERMSIG(status) << " - restart child"); + } + } + else if (WEXITSTATUS(status) == 111) + { + log_info_master("child requested restart"); + } + else + { + log_info_master("child exited with exitcode " << WEXITSTATUS(status)); + stop = true; + } + + if (unlink(pidFileName.c_str()) != 0) + log_error_master("failed to remove pidfile \"" << pidFileName << "\" error " << errno); + } + + void Tntnet::initWorkerProcess() + { + log_debug("init workerprocess"); + + signal(SIGPIPE, SIG_IGN); + signal(SIGABRT, SIG_IGN); + signal(SIGTERM, sigEnd); + signal(SIGHUP, sigReload); + + configureDispatcher(d_dispatcher, config); + + // create listener-threads + Tntconfig::config_entries_type configListen; + config.getConfigValues("Listen", configListen); + + if (configListen.empty()) + { + log_warn("no listeners defined - using 0.0.0.0:80"); + ListenerBase* s = new tnt::Listener("0.0.0.0", 80, queue); + listeners.insert(s); + } + else + { + for (Tntconfig::config_entries_type::const_iterator it = configListen.begin(); + it != configListen.end(); ++it) + { + if (it->params.empty()) + throw std::runtime_error("empty Listen-entry"); + + unsigned short int port = 80; + if (it->params.size() >= 2) + { + std::istringstream p(it->params[1]); + p >> port; + if (!p) + { + std::ostringstream msg; + msg << "invalid port " << it->params[1]; + throw std::runtime_error(msg.str()); + } + } + + std::string ip(it->params[0]); + log_debug("create listener ip=" << ip << " port=" << port); + ListenerBase* s = new tnt::Listener(ip, port, queue); + listeners.insert(s); + } + } + +#ifdef USE_SSL + // create ssl-listener-threads + std::string defaultCertificateFile = config.getValue("SslCertificate"); + std::string defaultCertificateKey = config.getValue("SslKey"); + configListen.clear(); + config.getConfigValues("SslListen", configListen); + + for (Tntconfig::config_entries_type::const_iterator it = configListen.begin(); + it != configListen.end(); ++it) + { + if (it->params.empty()) + throw std::runtime_error("empty SslListen-entry"); + + unsigned short int port = 443; + if (it->params.size() >= 2) + { + std::istringstream p(it->params[1]); + p >> port; + if (!p) + { + std::ostringstream msg; + msg << "invalid port " << it->params[1]; + throw std::runtime_error(msg.str()); + } + } + + std::string certificateFile = + it->params.size() >= 3 ? it->params[2] + : defaultCertificateFile; + std::string certificateKey = + it->params.size() >= 4 ? it->params[3] : + it->params.size() >= 3 ? it->params[2] : defaultCertificateKey; + + if (certificateFile.empty()) + throw std::runtime_error("Ssl-certificate not configured"); + + std::string ip(it->params[0]); + log_debug("create ssl-listener ip=" << ip << " port=" << port); + ListenerBase* s = new Ssllistener(certificateFile.c_str(), + certificateKey.c_str(), ip, port, queue); + listeners.insert(s); + } +#endif // USE_SSL + + // configure worker (static) + Comploader::configure(config); + + // configure http + HttpMessage::setMaxRequestSize( + config.getValue("MaxRequestSize", HttpMessage::getMaxRequestSize())); + Job::setSocketReadTimeout( + config.getValue("SocketReadTimeout", Job::getSocketReadTimeout())); + Job::setSocketWriteTimeout( + config.getValue("SocketWriteTimeout", Job::getSocketWriteTimeout())); + Job::setKeepAliveMax( + config.getValue("KeepAliveMax", Job::getKeepAliveMax())); + Job::setSocketBufferSize( + config.getValue("BufferSize", Job::getSocketBufferSize())); + HttpReply::setMinCompressSize( + config.getValue("MinCompressSize", HttpReply::getMinCompressSize())); + HttpReply::setKeepAliveTimeout( + config.getValue("KeepAliveTimeout", HttpReply::getKeepAliveTimeout())); + HttpReply::setDefaultContentType( + config.getValue("DefaultContentType", HttpReply::getDefaultContentType())); + + log_debug("listeners.size()=" << listeners.size()); + } + + void Tntnet::workerProcess(int filedes) + { + log_debug("worker-process"); + + // reload configuration + config = Tntconfig(); + loadConfiguration(); + + // initialize worker-process + minthreads = config.getValue("MinThreads", 5); + maxthreads = config.getValue("MaxThreads", 100); + threadstartdelay = config.getValue("ThreadStartDelay", 10); + Worker::setMinThreads(minthreads); + Worker::setMaxRequestTime(config.getValue("MaxRequestTime", Worker::getMaxRequestTime())); + Worker::setEnableCompression(config.getBoolValue("EnableCompression", Worker::getEnableCompression())); + queue.setCapacity(config.getValue("QueueSize", queue.getCapacity())); + Sessionscope::setDefaultTimeout(config.getValue("SessionTimeout", Sessionscope::getDefaultTimeout())); + Listener::setBacklog(config.getValue("ListenBacklog", Listener::getBacklog())); + Listener::setListenRetry(config.getValue("ListenRetry", Listener::getListenRetry())); + Dispatcher::setMaxUrlMapCache(config.getValue("MaxUrlMapCache", Dispatcher::getMaxUrlMapCache())); + + Tntconfig::config_entries_type configSetEnv; + config.getConfigValues("SetEnv", configSetEnv); + for (Tntconfig::config_entries_type::const_iterator it = configSetEnv.begin(); + it != configSetEnv.end(); ++it) + { + if (it->params.size() >= 2) + { +#ifdef HAVE_SETENV + log_debug("setenv " << it->params[0] << "=\"" << it->params[1] << '"'); + ::setenv(it->params[0].c_str(), it->params[1].c_str(), 1); +#else + std::string name = it->params[0]; + std::string value = it->params[1]; + + char* env = new char[name.size() + value.size() + 2]; + name.copy(env, name.size()); + env[name.size()] = '='; + value.copy(env + name.size() + 1, value.size()); + env[name.size() + value.size() + 1] = '\0'; + + log_debug("putenv(" << env); + ::putenv(env); +#endif + } + } + + // create worker-threads + log_info("create " << minthreads << " worker threads"); + for (unsigned i = 0; i < minthreads; ++i) + { + log_debug("create worker " << i); + Worker* s = new Worker(*this); + s->create(); + } + + // create poller-thread + log_debug("start poller thread"); + pollerthread.create(); + + // launch listener-threads + log_info("create " << listeners.size() << " listener threads"); + for (listeners_type::iterator it = listeners.begin(); + it != listeners.end(); ++it) + (*it)->create(); + + log_debug("start timer thread"); + cxxtools::MethodThread timerThread(*this, &Tntnet::timerTask); + timerThread.create(); + + if (filedes >= 0) + { + signalParentSuccess(filedes); + closeStdHandles(); + } + + // mainloop + cxxtools::Mutex mutex; + while (!stop) + { + { + cxxtools::MutexLock lock(mutex); + queue.noWaitThreads.wait(lock); + } + + if (stop) + break; + + if (Worker::getCountThreads() < maxthreads) + { + log_info("create workerthread"); + Worker* s = new Worker(*this); + s->create(); + } + else + log_warn("max worker-threadcount " << maxthreads << " reached"); + + if (threadstartdelay > 0) + usleep(threadstartdelay); + } + + log_warn("stopping Tntnet"); + + // join-loop + while (!listeners.empty()) + { + listeners_type::value_type s = *listeners.begin(); + log_debug("remove listener from listener-list"); + listeners.erase(s); + + log_debug("request listener to stop"); + s->doStop(); + + log_debug("join listener-thread"); + s->join(); + delete s; + + log_debug("listener stopped"); + } + + log_info("listeners stopped"); + } + + void Tntnet::timerTask() + { + log_debug("timer thread"); + + while (!stop) + { + sleep(1); + + log_debug("check sessiontimeout"); + getScopemanager().checkSessionTimeout(); + + log_debug("worker-timer"); + Worker::timer(); + } + + log_warn("stopping Tntnet"); + + if (!pidFileName.empty()) + unlink(pidFileName.c_str()); + + queue.noWaitThreads.signal(); + Worker::setMinThreads(0); + pollerthread.doStop(); + } + + void Tntnet::loadConfiguration() + { + config = Tntconfig(); + config.load(configFile.c_str()); + } + + void Tntnet::shutdown() + { + stop = true; + } + + void Tntnet::restart() + { + // stopping child with 111 signals monitor-process to restart child + stop = true; + ret = 111; + } + +} + +//////////////////////////////////////////////////////////////////////// +// main +// +#ifdef HAVE_TNTNET_MAIN +int main(int argc, char* argv[]) +{ + signal(SIGPIPE, SIG_IGN); + signal(SIGABRT, SIG_IGN); + signal(SIGTERM, sigEnd); + std::ios::sync_with_stdio(false); + + try + { + tnt::Tntnet app(argc, argv); + if (argc != 1) + { + std::cout << PACKAGE_STRING "\n\n" << + "usage: " << argv[0] << " {options}\n\n" + " -c file configurationfile (default: " TNTNET_CONF ")\n" + " -d enable all debug output (ignoring properties-file)\n"; + return -1; + } + + return app.run(); + } + catch(const std::exception& e) + { + log_fatal(e.what()); + std::cerr << e.what() << std::endl; + return -1; + } +} +#endif diff --git a/httpd/worker.cpp b/httpd/worker.cpp new file mode 100644 index 00000000..fc8a271e --- /dev/null +++ b/httpd/worker.cpp @@ -0,0 +1,365 @@ +/* worker.cpp + * Copyright (C) 2003-2005 Tommi Maekitalo + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and + * NON-INFRINGEMENT. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "tnt/worker.h" +#include "tnt/dispatcher.h" +#include "tnt/job.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +log_define("tntnet.worker") + +namespace +{ + static const char stateStarting[] = "0 starting"; + static const char stateWaitingForJob[] = "1 waiting for job"; + static const char stateParsing[] = "2 parsing request"; + static const char statePostParsing[] = "3 post parsing"; + static const char stateDispatch[] = "4 dispatch"; + static const char stateProcessingRequest[] = "5 processing request"; + static const char stateFlush[] = "6 flush"; + static const char stateSendReply[] = "7 send reply"; + static const char stateSendError[] = "8 send error"; + static const char stateStopping[] = "9 stopping"; +} + +namespace tnt +{ + cxxtools::Mutex Worker::mutex; + unsigned Worker::nextThreadNumber = 0; + Worker::workers_type Worker::workers; + unsigned Worker::maxRequestTime = 600; + unsigned Worker::minThreads = 5; + bool Worker::enableCompression = true; + + Worker::ComploaderPoolType Worker::comploaderPool; + + Worker::Worker(Tntnet& app) + : application(app), + comploaderObject(comploaderPool.get()), + comploader(comploaderObject), + threadId(0), + state(stateStarting), + lastWaitTime(0) + { + log_debug("initialize thread " << threadId); + + cxxtools::MutexLock lock(mutex); + workers.insert(this); + } + + Worker::~Worker() + { + cxxtools::MutexLock lock(mutex); + workers.erase(this); + comploader.cleanup(); + + log_debug("delete worker " << threadId << " - " << workers.size() << " threads left - " << application.getQueue().getWaitThreadCount() << " waiting threads"); + } + + void Worker::run() + { + threadId = pthread_self(); + Jobqueue& queue = application.getQueue(); + log_debug("start thread " << threadId); + while (queue.getWaitThreadCount() < minThreads) + { + log_debug("waiting for job"); + state = stateWaitingForJob; + Jobqueue::JobPtr j = queue.get(); + if (Tntnet::shouldStop()) + { + log_warn("stop worker"); + break; + } + log_debug("got job - fd=" << j->getFd()); + + std::iostream& socket = j->getStream(); + + try + { + bool keepAlive; + do + { + time(&lastWaitTime); + + log_debug("read request"); + + keepAlive = false; + state = stateParsing; + j->getParser().parse(socket); + state = statePostParsing; + + if (socket.eof()) + log_debug("eof"); + else if (j->getParser().failed()) + { + state = stateSendError; + log_warn("bad request"); + socket << "HTTP/1.0 500 bad request\r\n" + "Content-Type: text/html\r\n" + "\r\n" + "

Error

bad request

" + << std::endl; + } + else if (socket.fail()) + log_error("socket failed"); + else + { + j->getRequest().doPostParse(); + + j->setWrite(); + keepAlive = processRequest(j->getRequest(), socket, + j->decrementKeepAliveCounter()); + + if (keepAlive) + { + j->setRead(); + j->clear(); + + // if there is something to do and no threads waiting, we take + // the next job just to improve resposiveness. + if (queue.getWaitThreadCount() == 0 + && !queue.empty()) + { + application.getPoller().addIdleJob(j); + keepAlive = false; + } + else + { + struct pollfd fd; + fd.fd = j->getFd(); + fd.events = POLLIN; + if (::poll(&fd, 1, Job::getSocketReadTimeout()) == 0) + { + application.getPoller().addIdleJob(j); + keepAlive = false; + } + } + } + } + } while (keepAlive); + } + catch (const cxxtools::net::Timeout& e) + { + log_debug("timeout - put job in poller"); + application.getPoller().addIdleJob(j); + } + catch (const cxxtools::net::Exception& e) + { + if (e.getErrno() != ENOENT) + log_warn("unexpected exception: " << e.what()); + } + catch (const std::exception& e) + { + log_warn("unexpected exception: " << e.what()); + } + } + + time(&lastWaitTime); + + log_debug("end worker-thread " << threadId); + + state = stateStopping; + } + + bool Worker::processRequest(HttpRequest& request, std::iostream& socket, + unsigned keepAliveCount) + { + // log message + log_info("process request: " << request.getMethod() << ' ' << request.getQuery() + << " from client " << request.getPeerIp() << " user-Agent \"" << request.getUserAgent() + << '"'); + + // create reply-object + HttpReply reply(socket); + reply.setVersion(request.getMajorVersion(), request.getMinorVersion()); + reply.setMethod(request.getMethod()); + + std::locale loc = request.getLocale(); + reply.out().imbue(loc); + reply.sout().imbue(loc); + + if (request.keepAlive()) + reply.setKeepAliveCounter(keepAliveCount); + + if (enableCompression) + reply.setAcceptEncoding(request.getEncoding()); + + // process request + try + { + try + { + dispatch(request, reply); + + if (!request.keepAlive() || !reply.keepAlive()) + keepAliveCount = 0; + + if (keepAliveCount > 0) + log_debug("keep alive"); + else + { + log_debug("no keep alive request/reply=" + << request.keepAlive() << '/' << reply.keepAlive()); + } + } + catch (const HttpError& e) + { + throw; + } + catch (const std::exception& e) + { + throw HttpError(HTTP_INTERNAL_SERVER_ERROR, e.what()); + } + } + catch (const HttpError& e) + { + state = stateSendError; + log_warn("http-Error: " << e.what()); + HttpReply reply(socket); + reply.setVersion(request.getMajorVersion(), request.getMinorVersion()); + if (request.keepAlive()) + reply.setKeepAliveCounter(keepAliveCount); + else + keepAliveCount = 0; + reply.out() << "

Error

" + << e.what() << "

" << std::endl; + reply.sendReply(e.getErrcode(), e.getErrmsg()); + } + + return keepAliveCount > 0; + } + + void Worker::dispatch(HttpRequest& request, HttpReply& reply) + { + state = stateDispatch; + const std::string& url = request.getUrl(); + + log_debug("dispatch " << request.getQuery()); + + if (!HttpRequest::checkUrl(url)) + throw HttpError(HTTP_BAD_REQUEST, "illegal url"); + + request.setThreadScope(threadScope); + + Dispatcher::PosType pos(application.getDispatcher(), request.getUrl()); + while (true) + { + state = stateDispatch; + + // pos.getNext() throws NotFoundException at end + Dispatcher::CompidentType ci = pos.getNext(); + try + { + log_debug("load component " << ci); + Component& comp = comploader.fetchComp(ci, application.getDispatcher()); + request.setPathInfo(ci.hasPathInfo() ? ci.getPathInfo() : url); + request.setArgs(ci.getArgs()); + + application.getScopemanager().preCall(request, ci.libname); + + log_debug("call component " << ci << " path " << request.getPathInfo()); + state = stateProcessingRequest; + unsigned http_return = comp(request, reply, request.getQueryParams()); + if (http_return != DECLINED) + { + if (reply.isDirectMode()) + { + log_info("request ready, returncode " << http_return); + state = stateFlush; + reply.out().flush(); + } + else + { + log_info("request ready, returncode " << http_return << " - ContentSize: " << reply.getContentSize()); + + application.getScopemanager().postCall(request, reply, ci.libname); + + state = stateSendReply; + reply.sendReply(http_return); + } + + if (reply.out()) + log_debug("reply sent"); + else + log_warn("stream error"); + + return; + } + else + log_debug("component " << ci << " returned DECLINED"); + } + catch (const cxxtools::dl::DlopenError& e) + { + log_warn("DlopenError catched - libname " << e.getLibname()); + } + catch (const cxxtools::dl::SymbolNotFound& e) + { + log_warn("SymbolNotFound catched - symbol " << e.getSymbol()); + } + } + + throw NotFoundException(request.getUrl()); + } + + void Worker::timer() + { + time_t currentTime; + time(¤tTime); + + cxxtools::MutexLock lock(mutex); + for (workers_type::iterator it = workers.begin(); + it != workers.end(); ++it) + { + (*it)->healthCheck(currentTime); + } + } + + void Worker::healthCheck(time_t currentTime) + { + if (state == stateProcessingRequest + && lastWaitTime != 0 + && maxRequestTime > 0) + { + if (static_cast(currentTime - lastWaitTime) > maxRequestTime) + { + log_fatal("requesttime " << maxRequestTime << " seconds in thread " + << threadId << " exceeded - exit process"); + log_info("current state: " << state); + exit(111); + } + } + } + + Worker::workers_type::size_type Worker::getCountThreads() + { + cxxtools::MutexLock lock(mutex); + return workers.size(); + } +} diff --git a/live.cpp b/live.cpp new file mode 100644 index 00000000..da7c3006 --- /dev/null +++ b/live.cpp @@ -0,0 +1,86 @@ +/* + * httpd.c: A plugin for the Video Disk Recorder + * + * See the README file for copyright information and how to reach the author. + * + * $Id: live.cpp,v 1.1 2007/01/02 19:18:27 lordjaxom Exp $ + */ + +#include +#include +#include "setup.h" +#include "thread.h" + +namespace vdrlive { + +using namespace std; + +static const char *VERSION = "0.0.1"; +static const char *DESCRIPTION = "Enter description for 'httpd' plugin"; + +class Plugin : public cPlugin { +public: + Plugin(void); + virtual const char *Version(void) { return VERSION; } + virtual const char *Description(void) { return DESCRIPTION; } + virtual const char *CommandLineHelp(void); + virtual bool ProcessArgs(int argc, char *argv[]); + virtual bool Start(void); + virtual void Stop(void); + virtual void MainThreadHook(void); + virtual cString Active(void); + virtual cMenuSetupPage *SetupMenu(void); + virtual bool SetupParse(const char *Name, const char *Value); + +private: + auto_ptr< ServerThread > m_thread; +}; + +Plugin::Plugin(void) +{ +} + +const char *Plugin::CommandLineHelp(void) +{ + return "-L DIR --lib=DIR libtnt-live.so will be searched in DIR\n"; +} + +bool Plugin::ProcessArgs(int argc, char *argv[]) +{ + return Setup::Get().Parse( argc, argv ); +} + +bool Plugin::Start(void) +{ + // XXX error handling + m_thread.reset( new ServerThread ); + m_thread->Start(); + return true; +} + +void Plugin::Stop(void) +{ +} + +void Plugin::MainThreadHook(void) +{ +} + +cString Plugin::Active(void) +{ + return NULL; +} + +cMenuSetupPage *Plugin::SetupMenu(void) +{ + return NULL; +} + +bool Plugin::SetupParse(const char *Name, const char *Value) +{ + return true; +} + +} // namespace vdrlive + +VDRPLUGINCREATOR(vdrlive::Plugin); // Don't touch this! diff --git a/setup.cpp b/setup.cpp new file mode 100644 index 00000000..ab518dc8 --- /dev/null +++ b/setup.cpp @@ -0,0 +1,33 @@ +#include +#include "setup.h" + +namespace vdrlive { + +Setup::Setup() +{ +} + +bool Setup::Parse( int argc, char* argv[] ) +{ + static struct option opts[] = { + { "lib", required_argument, NULL, 'L' }, + { 0 } + }; + + int optchar, optind = 0; + while ( ( optchar = getopt_long( argc, argv, "L:", opts, &optind ) ) != -1 ) { + switch ( optchar ) { + case 'L': m_libraryPath = optarg; break; + default: return false; + } + } + return true; +} + +Setup& Setup::Get() +{ + static Setup instance; + return instance; +} + +} // namespace vdrlive diff --git a/setup.h b/setup.h new file mode 100644 index 00000000..d3a60277 --- /dev/null +++ b/setup.h @@ -0,0 +1,26 @@ +#ifndef VDR_LIVE_SETUP_H +#define VDR_LIVE_SETUP_H + +#include + +namespace vdrlive { + +class Setup +{ +public: + static Setup& Get(); + + std::string const& GetLibraryPath() const { return m_libraryPath; } + + bool Parse( int argc, char* argv[] ); + +private: + Setup(); + Setup( Setup const& ); + + std::string m_libraryPath; +}; + +} // namespace vdrlive + +#endif // VDR_LIVE_SETUP_H diff --git a/thread.cpp b/thread.cpp new file mode 100644 index 00000000..df6580ce --- /dev/null +++ b/thread.cpp @@ -0,0 +1,40 @@ +#include +#include +#include +#include +#include +#include "thread.h" +#include "tntconfig.h" + +namespace vdrlive { + +using namespace std; +using namespace tnt; + +ServerThread::ServerThread(): + m_configPath( 0 ) +{ +} + +ServerThread::~ServerThread() +{ + free( m_configPath ); +} + +void ServerThread::Action() +{ + try { + m_configPath = strdup( TntConfig::Get().GetConfigPath().c_str() ); + + char* argv[] = { "tntnet", "-c", m_configPath }; + int argc = sizeof( argv ) / sizeof( argv[0] ); + Tntnet app( argc, argv ); + app.run(); + } catch ( exception const& ex ) { + // XXX move initial error handling to live.cpp + esyslog( "ERROR: live httpd server crashed: %s", ex.what() ); + cerr << "HTTPD FATAL ERROR: " << ex.what() << endl; + } +} + +} // namespace vdrlive diff --git a/thread.h b/thread.h new file mode 100644 index 00000000..d0a3bcac --- /dev/null +++ b/thread.h @@ -0,0 +1,22 @@ +#ifndef VDR_LIVE_THREAD_H +#define VDR_LIVE_THREAD_H + +#include + +namespace vdrlive { + +class ServerThread : public cThread { +public: + ServerThread(); + virtual ~ServerThread(); + +protected: + virtual void Action(); + +private: + char* m_configPath; +}; + +} // namespace vdrlive + +#endif // VDR_LIVE_THREAD_H diff --git a/tntconfig.cpp b/tntconfig.cpp new file mode 100644 index 00000000..78e3f454 --- /dev/null +++ b/tntconfig.cpp @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include +#include +#include "setup.h" +#include "tntconfig.h" + +namespace vdrlive { + +using namespace std; + +TntConfig::TntConfig() +{ + WriteConfig(); +} + +void TntConfig::WriteConfig() +{ + WriteProperties(); + + ostringstream builder; + builder << cPlugin::ConfigDirectory( PLUGIN_NAME_I18N ) << "/httpd.config"; + m_configPath = builder.str(); + + ofstream file( m_configPath.c_str(), ios::out | ios::trunc ); + if ( !file ) { + ostringstream builder; + builder << "Can't open " << m_configPath << " for writing: " << strerror( errno ); + throw runtime_error( builder.str() ); + } + + // XXX modularize + file << "MapUrl /([^.]+)(\\..+)? $1@libtnt-live" << std::endl; + file << "Listen 0.0.0.0 8001" << std::endl; + file << "PropertyFile " << m_propertiesPath << std::endl; + file << "CompPath " << Setup::Get().GetLibraryPath() << "/" << std::endl; +} + +void TntConfig::WriteProperties() +{ + ostringstream builder; + builder << cPlugin::ConfigDirectory( PLUGIN_NAME_I18N ) << "/httpd.properties"; + m_propertiesPath = builder.str(); + + ofstream file( m_propertiesPath.c_str(), ios::out | ios::trunc ); + if ( !file ) { + ostringstream builder; + builder << "Can't open " << m_propertiesPath << " for writing: " << strerror( errno ); + throw runtime_error( builder.str() ); + } + + // XXX modularize + file << "rootLogger=INFO" << std::endl; + file << "logger.tntnet=INFO" << std::endl; +} + +TntConfig const& TntConfig::Get() +{ + static TntConfig instance; + return instance; +} + +} // namespace vdrlive diff --git a/tntconfig.h b/tntconfig.h new file mode 100644 index 00000000..ba31f977 --- /dev/null +++ b/tntconfig.h @@ -0,0 +1,28 @@ +#ifndef VDR_LIVE_TNTCONFIG_H +#define VDR_LIVE_TNTCONFIG_H + +#include + +namespace vdrlive { + +class TntConfig +{ +public: + static TntConfig const& Get(); + + std::string const& GetConfigPath() const { return m_configPath; } + +private: + std::string m_propertiesPath; + std::string m_configPath; + + TntConfig(); + TntConfig( TntConfig const& ); + + void WriteProperties(); + void WriteConfig(); +}; + +} // namespace vdrlive + +#endif // VDR_LIVE_TNTCONFIG_H