summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/CuTest-README.txt225
-rw-r--r--test/CuTest.c367
-rw-r--r--test/CuTest.h146
-rw-r--r--test/serf_bwtp.c635
-rw-r--r--test/serf_get.c503
-rw-r--r--test/serf_request.c79
-rw-r--r--test/serf_response.c161
-rw-r--r--test/serf_server.c147
-rw-r--r--test/serf_spider.c826
-rw-r--r--test/serftestca.pem66
-rw-r--r--test/server/test_server.c359
-rw-r--r--test/server/test_server.h70
-rw-r--r--test/test_all.c101
-rw-r--r--test/test_buckets.c425
-rw-r--r--test/test_context.c1011
-rw-r--r--test/test_serf.h126
-rw-r--r--test/test_ssl.c112
-rw-r--r--test/test_util.c214
-rw-r--r--test/testcases/chunked-empty.response11
-rw-r--r--test/testcases/chunked-trailers.response14
-rw-r--r--test/testcases/chunked.response13
-rw-r--r--test/testcases/deflate.responsebin0 -> 639 bytes
-rw-r--r--test/testcases/simple.request1
-rw-r--r--test/testcases/simple.response32
24 files changed, 5644 insertions, 0 deletions
diff --git a/test/CuTest-README.txt b/test/CuTest-README.txt
new file mode 100644
index 0000000..2e90234
--- /dev/null
+++ b/test/CuTest-README.txt
@@ -0,0 +1,225 @@
+Originally obtained from "http://cutest.sourceforge.net/" version 1.4.
+
+HOW TO USE
+
+You can use CuTest to create unit tests to drive your development
+in the style of Extreme Programming. You can also add unit tests to
+existing code to ensure that it works as you suspect.
+
+Your unit tests are an investment. They let you to change your
+code and add new features confidently without worrying about
+accidentally breaking earlier features.
+
+
+LICENSING
+
+Copyright (c) 2003 Asim Jalis
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+claim that you wrote the original software. If you use this software in
+a product, an acknowledgment in the product documentation would be
+appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and must not
+be misrepresented as being the original software.
+
+3. This notice may not be removed or altered from any source
+distribution.
+
+
+GETTING STARTED
+
+To add unit testing to your C code the only files you need are
+CuTest.c and CuTest.h.
+
+CuTestTest.c and AllTests.c have been included to provide an
+example of how to write unit tests and then how to aggregate them
+into suites and into a single AllTests.c file. Suites allow you
+to put group tests into logical sets. AllTests.c combines all the
+suites and runs them.
+
+You should not have to look inside CuTest.c. Looking in
+CuTestTest.c and AllTests.c (for example usage) should be
+sufficient.
+
+After downloading the sources, run your compiler to create an
+executable called AllTests.exe. For example, if you are using
+Windows with the cl.exe compiler you would type:
+
+ cl.exe AllTests.c CuTest.c CuTestTest.c
+ AllTests.exe
+
+This will run all the unit tests associated with CuTest and print
+the output on the console. You can replace cl.exe with gcc or
+your favorite compiler in the command above.
+
+
+DETAILED EXAMPLE
+
+Here is a more detailed example. We will work through a simple
+test first exercise. The goal is to create a library of string
+utilities. First, lets write a function that converts a
+null-terminated string to all upper case.
+
+Ensure that CuTest.c and CuTest.h are accessible from your C
+project. Next, create a file called StrUtil.c with these
+contents:
+
+ #include "CuTest.h"
+
+ char* StrToUpper(char* str) {
+ return str;
+ }
+
+ void TestStrToUpper(CuTest *tc) {
+ char* input = strdup("hello world");
+ char* actual = StrToUpper(input);
+ char* expected = "HELLO WORLD";
+ CuAssertStrEquals(tc, expected, actual);
+ }
+
+ CuSuite* StrUtilGetSuite() {
+ CuSuite* suite = CuSuiteNew();
+ SUITE_ADD_TEST(suite, TestStrToUpper);
+ return suite;
+ }
+
+Create another file called AllTests.c with these contents:
+
+ #include "CuTest.h"
+
+ CuSuite* StrUtilGetSuite();
+
+ void RunAllTests(void) {
+ CuString *output = CuStringNew();
+ CuSuite* suite = CuSuiteNew();
+
+ CuSuiteAddSuite(suite, StrUtilGetSuite());
+
+ CuSuiteRun(suite);
+ CuSuiteSummary(suite, output);
+ CuSuiteDetails(suite, output);
+ printf("%s\n", output->buffer);
+ }
+
+ int main(void) {
+ RunAllTests();
+ }
+
+Then type this on the command line:
+
+ gcc AllTests.c CuTest.c StrUtil.c
+
+to compile. You can replace gcc with your favorite compiler.
+CuTest should be portable enough to handle all Windows and Unix
+compilers. Then to run the tests type:
+
+ a.out
+
+This will print an error because we haven't implemented the
+StrToUpper function correctly. We are just returning the string
+without changing it to upper case.
+
+ char* StrToUpper(char* str) {
+ return str;
+ }
+
+Rewrite this as follows:
+
+ char* StrToUpper(char* str) {
+ char* p;
+ for (p = str ; *p ; ++p) *p = toupper(*p);
+ return str;
+ }
+
+Recompile and run the tests again. The test should pass this
+time.
+
+
+WHAT TO DO NEXT
+
+At this point you might want to write more tests for the
+StrToUpper function. Here are some ideas:
+
+TestStrToUpper_EmptyString : pass in ""
+TestStrToUpper_UpperCase : pass in "HELLO WORLD"
+TestStrToUpper_MixedCase : pass in "HELLO world"
+TestStrToUpper_Numbers : pass in "1234 hello"
+
+As you write each one of these tests add it to StrUtilGetSuite
+function. If you don't the tests won't be run. Later as you write
+other functions and write tests for them be sure to include those
+in StrUtilGetSuite also. The StrUtilGetSuite function should
+include all the tests in StrUtil.c
+
+Over time you will create another file called FunkyStuff.c
+containing other functions unrelated to StrUtil. Follow the same
+pattern. Create a FunkyStuffGetSuite function in FunkyStuff.c.
+And add FunkyStuffGetSuite to AllTests.c.
+
+The framework is designed in the way it is so that it is easy to
+organize a lot of tests.
+
+THE BIG PICTURE
+
+Each individual test corresponds to a CuTest. These are grouped
+to form a CuSuite. CuSuites can hold CuTests or other CuSuites.
+AllTests.c collects all the CuSuites in the program into a single
+CuSuite which it then runs as a single CuSuite.
+
+The project is open source so feel free to take a peek under the
+hood at the CuTest.c file to see how it works. CuTestTest.c
+contains tests for CuTest.c. So CuTest tests itself.
+
+Since AllTests.c has a main() you will need to exclude this when
+you are building your product. Here is a nicer way to do this if
+you want to avoid messing with multiple builds. Remove the main()
+in AllTests.c. Note that it just calls RunAllTests(). Instead
+we'll call this directly from the main program.
+
+Now in the main() of the actual program check to see if the
+command line option "--test" was passed. If it was then I call
+RunAllTests() from AllTests.c. Otherwise run the real program.
+
+Shipping the tests with the code can be useful. If you customers
+complain about a problem you can ask them to run the unit tests
+and send you the output. This can help you to quickly isolate the
+piece of your system that is malfunctioning in the customer's
+environment.
+
+CuTest offers a rich set of CuAssert functions. Here is a list:
+
+void CuAssert(CuTest* tc, char* message, int condition);
+void CuAssertTrue(CuTest* tc, int condition);
+void CuAssertStrEquals(CuTest* tc, char* expected, char* actual);
+void CuAssertIntEquals(CuTest* tc, int expected, int actual);
+void CuAssertPtrEquals(CuTest* tc, void* expected, void* actual);
+void CuAssertPtrNotNull(CuTest* tc, void* pointer);
+
+The project is open source and so you can add other more powerful
+asserts to make your tests easier to write and more concise.
+
+
+AUTOMATING TEST SUITE GENERATION
+
+make-tests.sh will grep through all the .c files in the current
+directory and generate the code to run all the tests contained in
+them. Using this script you don't have to worry about writing
+AllTests.c or dealing with any of the other suite code.
+
+
+CREDITS
+
+[02.23.2003] Dave Glowacki has added
+(1) file name and line numbers to the error messages, (2)
+AssertDblEquals for doubles, (3) Assert<X>Equals_Msg version of
+all the Assert<X>Equals to pass in optional message which is
+printed out on assert failure.
diff --git a/test/CuTest.c b/test/CuTest.c
new file mode 100644
index 0000000..315f59f
--- /dev/null
+++ b/test/CuTest.c
@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) 2003 Asim Jalis
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any
+ * damages arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any
+ * purpose, including commercial applications, and to alter it and
+ * redistribute it freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you
+ * must not claim that you wrote the original software. If you use
+ * this software in a product, an acknowledgment in the product
+ * documentation would be appreciated but is not required.
+ *
+ * 2. Altered source versions must be plainly marked as such, and
+ * must not be misrepresented as being the original software.
+ *
+ * 3. This notice may not be removed or altered from any source
+ * distribution.
+ *-------------------------------------------------------------------------*
+ *
+ * Originally obtained from "http://cutest.sourceforge.net/" version 1.4.
+ *
+ * Modified for serf as follows
+ * 1) added CuStringFree(), CuTestFree(), CuSuiteFree(), and
+ * CuSuiteFreeDeep()
+ * 0) reformatted the whitespace (doh!)
+ */
+#include <assert.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+#include "CuTest.h"
+
+/*-------------------------------------------------------------------------*
+ * CuStr
+ *-------------------------------------------------------------------------*/
+
+char* CuStrAlloc(int size)
+{
+ char* newStr = (char*) malloc( sizeof(char) * (size) );
+ return newStr;
+}
+
+char* CuStrCopy(const char* old)
+{
+ int len = strlen(old);
+ char* newStr = CuStrAlloc(len + 1);
+ strcpy(newStr, old);
+ return newStr;
+}
+
+/*-------------------------------------------------------------------------*
+ * CuString
+ *-------------------------------------------------------------------------*/
+
+void CuStringInit(CuString* str)
+{
+ str->length = 0;
+ str->size = STRING_MAX;
+ str->buffer = (char*) malloc(sizeof(char) * str->size);
+ str->buffer[0] = '\0';
+}
+
+CuString* CuStringNew(void)
+{
+ CuString* str = (CuString*) malloc(sizeof(CuString));
+ str->length = 0;
+ str->size = STRING_MAX;
+ str->buffer = (char*) malloc(sizeof(char) * str->size);
+ str->buffer[0] = '\0';
+ return str;
+}
+
+void CuStringFree(CuString *str)
+{
+ free(str->buffer);
+ free(str);
+}
+
+void CuStringResize(CuString* str, int newSize)
+{
+ str->buffer = (char*) realloc(str->buffer, sizeof(char) * newSize);
+ str->size = newSize;
+}
+
+void CuStringAppend(CuString* str, const char* text)
+{
+ int length;
+
+ if (text == NULL) {
+ text = "NULL";
+ }
+
+ length = strlen(text);
+ if (str->length + length + 1 >= str->size)
+ CuStringResize(str, str->length + length + 1 + STRING_INC);
+ str->length += length;
+ strcat(str->buffer, text);
+}
+
+void CuStringAppendChar(CuString* str, char ch)
+{
+ char text[2];
+ text[0] = ch;
+ text[1] = '\0';
+ CuStringAppend(str, text);
+}
+
+void CuStringAppendFormat(CuString* str, const char* format, ...)
+{
+ va_list argp;
+ char buf[HUGE_STRING_LEN];
+ va_start(argp, format);
+ vsprintf(buf, format, argp);
+ va_end(argp);
+ CuStringAppend(str, buf);
+}
+
+void CuStringInsert(CuString* str, const char* text, int pos)
+{
+ int length = strlen(text);
+ if (pos > str->length)
+ pos = str->length;
+ if (str->length + length + 1 >= str->size)
+ CuStringResize(str, str->length + length + 1 + STRING_INC);
+ memmove(str->buffer + pos + length, str->buffer + pos, (str->length - pos) + 1);
+ str->length += length;
+ memcpy(str->buffer + pos, text, length);
+}
+
+/*-------------------------------------------------------------------------*
+ * CuTest
+ *-------------------------------------------------------------------------*/
+
+void CuTestInit(CuTest* t, const char* name, TestFunction function)
+{
+ t->name = CuStrCopy(name);
+ t->failed = 0;
+ t->ran = 0;
+ t->message = NULL;
+ t->function = function;
+ t->jumpBuf = NULL;
+}
+
+CuTest* CuTestNew(const char* name, TestFunction function)
+{
+ CuTest* tc = CU_ALLOC(CuTest);
+ CuTestInit(tc, name, function);
+ return tc;
+}
+
+void CuTestFree(CuTest* tc)
+{
+ free(tc->name);
+ free(tc);
+}
+
+void CuTestRun(CuTest* tc)
+{
+ jmp_buf buf;
+ tc->jumpBuf = &buf;
+ if (setjmp(buf) == 0)
+ {
+ tc->ran = 1;
+ (tc->function)(tc);
+ }
+ tc->jumpBuf = 0;
+}
+
+static void CuFailInternal(CuTest* tc, const char* file, int line, CuString* string)
+{
+ char buf[HUGE_STRING_LEN];
+
+ sprintf(buf, "%s:%d: ", file, line);
+ CuStringInsert(string, buf, 0);
+
+ tc->failed = 1;
+ tc->message = string->buffer;
+ if (tc->jumpBuf != 0) longjmp(*(tc->jumpBuf), 0);
+}
+
+void CuFail_Line(CuTest* tc, const char* file, int line, const char* message2, const char* message)
+{
+ CuString string;
+
+ CuStringInit(&string);
+ if (message2 != NULL)
+ {
+ CuStringAppend(&string, message2);
+ CuStringAppend(&string, ": ");
+ }
+ CuStringAppend(&string, message);
+ CuFailInternal(tc, file, line, &string);
+}
+
+void CuAssert_Line(CuTest* tc, const char* file, int line, const char* message, int condition)
+{
+ if (condition) return;
+ CuFail_Line(tc, file, line, NULL, message);
+}
+
+void CuAssertStrEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message,
+ const char* expected, const char* actual)
+{
+ CuString string;
+ if ((expected == NULL && actual == NULL) ||
+ (expected != NULL && actual != NULL &&
+ strcmp(expected, actual) == 0))
+ {
+ return;
+ }
+
+ CuStringInit(&string);
+ if (message != NULL)
+ {
+ CuStringAppend(&string, message);
+ CuStringAppend(&string, ": ");
+ }
+ CuStringAppend(&string, "expected <");
+ CuStringAppend(&string, expected);
+ CuStringAppend(&string, "> but was <");
+ CuStringAppend(&string, actual);
+ CuStringAppend(&string, ">");
+ CuFailInternal(tc, file, line, &string);
+}
+
+void CuAssertIntEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message,
+ int expected, int actual)
+{
+ char buf[STRING_MAX];
+ if (expected == actual) return;
+ sprintf(buf, "expected <%d> but was <%d>", expected, actual);
+ CuFail_Line(tc, file, line, message, buf);
+}
+
+void CuAssertDblEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message,
+ double expected, double actual, double delta)
+{
+ char buf[STRING_MAX];
+ if (fabs(expected - actual) <= delta) return;
+ sprintf(buf, "expected <%lf> but was <%lf>", expected, actual);
+ CuFail_Line(tc, file, line, message, buf);
+}
+
+void CuAssertPtrEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message,
+ void* expected, void* actual)
+{
+ char buf[STRING_MAX];
+ if (expected == actual) return;
+ sprintf(buf, "expected pointer <0x%p> but was <0x%p>", expected, actual);
+ CuFail_Line(tc, file, line, message, buf);
+}
+
+
+/*-------------------------------------------------------------------------*
+ * CuSuite
+ *-------------------------------------------------------------------------*/
+
+void CuSuiteInit(CuSuite* testSuite)
+{
+ testSuite->count = 0;
+ testSuite->failCount = 0;
+}
+
+CuSuite* CuSuiteNew(void)
+{
+ CuSuite* testSuite = CU_ALLOC(CuSuite);
+ CuSuiteInit(testSuite);
+ return testSuite;
+}
+
+void CuSuiteFree(CuSuite *testSuite)
+{
+ free(testSuite);
+}
+
+void CuSuiteFreeDeep(CuSuite *testSuite)
+{
+ int i;
+ for (i = 0 ; i < testSuite->count ; ++i)
+ {
+ CuTest* testCase = testSuite->list[i];
+ CuTestFree(testCase);
+ }
+ free(testSuite);
+}
+
+void CuSuiteAdd(CuSuite* testSuite, CuTest *testCase)
+{
+ assert(testSuite->count < MAX_TEST_CASES);
+ testSuite->list[testSuite->count] = testCase;
+ testSuite->count++;
+}
+
+void CuSuiteAddSuite(CuSuite* testSuite, CuSuite* testSuite2)
+{
+ int i;
+ for (i = 0 ; i < testSuite2->count ; ++i)
+ {
+ CuTest* testCase = testSuite2->list[i];
+ CuSuiteAdd(testSuite, testCase);
+ }
+}
+
+void CuSuiteRun(CuSuite* testSuite)
+{
+ int i;
+ for (i = 0 ; i < testSuite->count ; ++i)
+ {
+ CuTest* testCase = testSuite->list[i];
+ CuTestRun(testCase);
+ if (testCase->failed) { testSuite->failCount += 1; }
+ }
+}
+
+void CuSuiteSummary(CuSuite* testSuite, CuString* summary)
+{
+ int i;
+ for (i = 0 ; i < testSuite->count ; ++i)
+ {
+ CuTest* testCase = testSuite->list[i];
+ CuStringAppend(summary, testCase->failed ? "F" : ".");
+ }
+ CuStringAppend(summary, "\n\n");
+}
+
+void CuSuiteDetails(CuSuite* testSuite, CuString* details)
+{
+ int i;
+ int failCount = 0;
+
+ if (testSuite->failCount == 0)
+ {
+ int passCount = testSuite->count - testSuite->failCount;
+ const char* testWord = passCount == 1 ? "test" : "tests";
+ CuStringAppendFormat(details, "OK (%d %s)\n", passCount, testWord);
+ }
+ else
+ {
+ if (testSuite->failCount == 1)
+ CuStringAppend(details, "There was 1 failure:\n");
+ else
+ CuStringAppendFormat(details, "There were %d failures:\n", testSuite->failCount);
+
+ for (i = 0 ; i < testSuite->count ; ++i)
+ {
+ CuTest* testCase = testSuite->list[i];
+ if (testCase->failed)
+ {
+ failCount++;
+ CuStringAppendFormat(details, "%d) %s: %s\n",
+ failCount, testCase->name, testCase->message);
+ }
+ }
+ CuStringAppend(details, "\n!!!FAILURES!!!\n");
+
+ CuStringAppendFormat(details, "Runs: %d ", testSuite->count);
+ CuStringAppendFormat(details, "Passes: %d ", testSuite->count - testSuite->failCount);
+ CuStringAppendFormat(details, "Fails: %d\n", testSuite->failCount);
+ }
+}
diff --git a/test/CuTest.h b/test/CuTest.h
new file mode 100644
index 0000000..1895bec
--- /dev/null
+++ b/test/CuTest.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2003 Asim Jalis
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any
+ * damages arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any
+ * purpose, including commercial applications, and to alter it and
+ * redistribute it freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you
+ * must not claim that you wrote the original software. If you use
+ * this software in a product, an acknowledgment in the product
+ * documentation would be appreciated but is not required.
+ *
+ * 2. Altered source versions must be plainly marked as such, and
+ * must not be misrepresented as being the original software.
+ *
+ * 3. This notice may not be removed or altered from any source
+ * distribution.
+ *-------------------------------------------------------------------------*
+ *
+ * Originally obtained from "http://cutest.sourceforge.net/" version 1.4.
+ *
+ * Modified for serf as follows
+ * 2) removed const from struct CuTest.name
+ * 1) added CuStringFree(), CuTestFree(), CuSuiteFree(), and
+ * CuSuiteFreeDeep()
+ * 0) reformatted the whitespace (doh!)
+ */
+#ifndef CU_TEST_H
+#define CU_TEST_H
+
+#include <setjmp.h>
+#include <stdarg.h>
+
+/* CuString */
+
+char* CuStrAlloc(int size);
+char* CuStrCopy(const char* old);
+
+#define CU_ALLOC(TYPE) ((TYPE*) malloc(sizeof(TYPE)))
+
+#define HUGE_STRING_LEN 8192
+#define STRING_MAX 256
+#define STRING_INC 256
+
+typedef struct
+{
+ int length;
+ int size;
+ char* buffer;
+} CuString;
+
+void CuStringInit(CuString* str);
+CuString* CuStringNew(void);
+void CuStringFree(CuString *str);
+void CuStringRead(CuString* str, const char* path);
+void CuStringAppend(CuString* str, const char* text);
+void CuStringAppendChar(CuString* str, char ch);
+void CuStringAppendFormat(CuString* str, const char* format, ...);
+void CuStringInsert(CuString* str, const char* text, int pos);
+void CuStringResize(CuString* str, int newSize);
+
+/* CuTest */
+
+typedef struct CuTest CuTest;
+
+typedef void (*TestFunction)(CuTest *);
+
+struct CuTest
+{
+ char* name;
+ TestFunction function;
+ int failed;
+ int ran;
+ const char* message;
+ jmp_buf *jumpBuf;
+};
+
+void CuTestInit(CuTest* t, const char* name, TestFunction function);
+CuTest* CuTestNew(const char* name, TestFunction function);
+void CuTestFree(CuTest* tc);
+void CuTestRun(CuTest* tc);
+
+/* Internal versions of assert functions -- use the public versions */
+void CuFail_Line(CuTest* tc, const char* file, int line, const char* message2, const char* message);
+void CuAssert_Line(CuTest* tc, const char* file, int line, const char* message, int condition);
+void CuAssertStrEquals_LineMsg(CuTest* tc,
+ const char* file, int line, const char* message,
+ const char* expected, const char* actual);
+void CuAssertIntEquals_LineMsg(CuTest* tc,
+ const char* file, int line, const char* message,
+ int expected, int actual);
+void CuAssertDblEquals_LineMsg(CuTest* tc,
+ const char* file, int line, const char* message,
+ double expected, double actual, double delta);
+void CuAssertPtrEquals_LineMsg(CuTest* tc,
+ const char* file, int line, const char* message,
+ void* expected, void* actual);
+
+/* public assert functions */
+
+#define CuFail(tc, ms) CuFail_Line( (tc), __FILE__, __LINE__, NULL, (ms))
+#define CuAssert(tc, ms, cond) CuAssert_Line((tc), __FILE__, __LINE__, (ms), (cond))
+#define CuAssertTrue(tc, cond) CuAssert_Line((tc), __FILE__, __LINE__, "assert failed", (cond))
+
+#define CuAssertStrEquals(tc,ex,ac) CuAssertStrEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac))
+#define CuAssertStrEquals_Msg(tc,ms,ex,ac) CuAssertStrEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac))
+#define CuAssertIntEquals(tc,ex,ac) CuAssertIntEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac))
+#define CuAssertIntEquals_Msg(tc,ms,ex,ac) CuAssertIntEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac))
+#define CuAssertDblEquals(tc,ex,ac,dl) CuAssertDblEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac),(dl))
+#define CuAssertDblEquals_Msg(tc,ms,ex,ac,dl) CuAssertDblEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac),(dl))
+#define CuAssertPtrEquals(tc,ex,ac) CuAssertPtrEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac))
+#define CuAssertPtrEquals_Msg(tc,ms,ex,ac) CuAssertPtrEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac))
+
+#define CuAssertPtrNotNull(tc,p) CuAssert_Line((tc),__FILE__,__LINE__,"null pointer unexpected",(p != NULL))
+#define CuAssertPtrNotNullMsg(tc,msg,p) CuAssert_Line((tc),__FILE__,__LINE__,(msg),(p != NULL))
+
+/* CuSuite */
+
+#define MAX_TEST_CASES 1024
+
+#define SUITE_ADD_TEST(SUITE,TEST) CuSuiteAdd(SUITE, CuTestNew(#TEST, TEST))
+
+typedef struct
+{
+ int count;
+ CuTest* list[MAX_TEST_CASES];
+ int failCount;
+
+} CuSuite;
+
+
+void CuSuiteInit(CuSuite* testSuite);
+CuSuite* CuSuiteNew(void);
+void CuSuiteFree(CuSuite *testSuite);
+void CuSuiteFreeDeep(CuSuite *testSuite);
+void CuSuiteAdd(CuSuite* testSuite, CuTest *testCase);
+void CuSuiteAddSuite(CuSuite* testSuite, CuSuite* testSuite2);
+void CuSuiteRun(CuSuite* testSuite);
+void CuSuiteSummary(CuSuite* testSuite, CuString* summary);
+void CuSuiteDetails(CuSuite* testSuite, CuString* details);
+
+#endif /* CU_TEST_H */
diff --git a/test/serf_bwtp.c b/test/serf_bwtp.c
new file mode 100644
index 0000000..0e73a2d
--- /dev/null
+++ b/test/serf_bwtp.c
@@ -0,0 +1,635 @@
+/* Copyright 2002-2004 Justin Erenkrantz and Greg Stein
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+
+#include <apr.h>
+#include <apr_uri.h>
+#include <apr_strings.h>
+#include <apr_atomic.h>
+#include <apr_base64.h>
+#include <apr_getopt.h>
+#include <apr_version.h>
+
+#include "serf.h"
+
+typedef struct {
+ int count;
+ int using_ssl;
+ serf_ssl_context_t *ssl_ctx;
+ serf_bucket_alloc_t *bkt_alloc;
+} app_baton_t;
+
+typedef struct {
+#if APR_MAJOR_VERSION > 0
+ apr_uint32_t requests_outstanding;
+#else
+ apr_atomic_t requests_outstanding;
+#endif
+ int print_headers;
+
+ serf_response_acceptor_t acceptor;
+ app_baton_t *acceptor_baton;
+
+ serf_response_handler_t handler;
+
+ const char *host;
+ const char *method;
+ const char *path;
+ const char *req_body_path;
+ const char *authn;
+} handler_baton_t;
+
+/* Kludges for APR 0.9 support. */
+#if APR_MAJOR_VERSION == 0
+#define apr_atomic_inc32 apr_atomic_inc
+#define apr_atomic_dec32 apr_atomic_dec
+#define apr_atomic_read32 apr_atomic_read
+#endif
+
+static void closed_connection(serf_connection_t *conn,
+ void *closed_baton,
+ apr_status_t why,
+ apr_pool_t *pool)
+{
+ if (why) {
+ abort();
+ }
+}
+
+static apr_status_t ignore_all_cert_errors(void *data, int failures,
+ const serf_ssl_certificate_t *cert)
+{
+ /* In a real application, you would normally would not want to do this */
+ return APR_SUCCESS;
+}
+
+static apr_status_t conn_setup(apr_socket_t *skt,
+ serf_bucket_t **input_bkt,
+ serf_bucket_t **output_bkt,
+ void *setup_baton,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *c;
+ app_baton_t *ctx = setup_baton;
+
+ c = serf_bucket_socket_create(skt, ctx->bkt_alloc);
+ if (ctx->using_ssl) {
+ c = serf_bucket_ssl_decrypt_create(c, ctx->ssl_ctx, ctx->bkt_alloc);
+ if (!ctx->ssl_ctx) {
+ ctx->ssl_ctx = serf_bucket_ssl_decrypt_context_get(c);
+ }
+ serf_ssl_server_cert_callback_set(ctx->ssl_ctx, ignore_all_cert_errors, NULL);
+
+ *output_bkt = serf_bucket_ssl_encrypt_create(*output_bkt, ctx->ssl_ctx,
+ ctx->bkt_alloc);
+ }
+
+ *input_bkt = c;
+
+ return APR_SUCCESS;
+}
+
+static serf_bucket_t* accept_response(serf_request_t *request,
+ serf_bucket_t *stream,
+ void *acceptor_baton,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *c;
+ serf_bucket_alloc_t *bkt_alloc;
+
+ /* get the per-request bucket allocator */
+ bkt_alloc = serf_request_get_alloc(request);
+
+ /* Create a barrier so the response doesn't eat us! */
+ c = serf_bucket_barrier_create(stream, bkt_alloc);
+
+ return serf_bucket_response_create(c, bkt_alloc);
+}
+
+static serf_bucket_t* accept_bwtp(serf_request_t *request,
+ serf_bucket_t *stream,
+ void *acceptor_baton,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *c;
+ app_baton_t *app_ctx = acceptor_baton;
+
+ /* Create a barrier so the response doesn't eat us! */
+ c = serf_bucket_barrier_create(stream, app_ctx->bkt_alloc);
+
+ return serf_bucket_bwtp_incoming_frame_create(c, app_ctx->bkt_alloc);
+}
+
+/* fwd declare */
+static apr_status_t handle_bwtp_upgrade(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *pool);
+
+static apr_status_t setup_request(serf_request_t *request,
+ void *setup_baton,
+ serf_bucket_t **req_bkt,
+ serf_response_acceptor_t *acceptor,
+ void **acceptor_baton,
+ serf_response_handler_t *handler,
+ void **handler_baton,
+ apr_pool_t *pool)
+{
+ handler_baton_t *ctx = setup_baton;
+ serf_bucket_t *hdrs_bkt;
+ serf_bucket_t *body_bkt;
+
+ if (ctx->req_body_path) {
+ apr_file_t *file;
+ apr_status_t status;
+
+ status = apr_file_open(&file, ctx->req_body_path, APR_READ,
+ APR_OS_DEFAULT, pool);
+
+ if (status) {
+ printf("Error opening file (%s)\n", ctx->req_body_path);
+ return status;
+ }
+
+ body_bkt = serf_bucket_file_create(file,
+ serf_request_get_alloc(request));
+ }
+ else {
+ body_bkt = NULL;
+ }
+
+ /*
+ *req_bkt = serf_bucket_bwtp_message_create(0, body_bkt,
+ serf_request_get_alloc(request));
+ */
+
+ *req_bkt = serf_bucket_bwtp_header_create(0, "MESSAGE",
+ serf_request_get_alloc(request));
+
+ hdrs_bkt = serf_bucket_bwtp_frame_get_headers(*req_bkt);
+
+ /* FIXME: Shouldn't we be able to figure out the host ourselves? */
+ serf_bucket_headers_setn(hdrs_bkt, "Host", ctx->host);
+ serf_bucket_headers_setn(hdrs_bkt, "User-Agent",
+ "Serf/" SERF_VERSION_STRING);
+ /* Shouldn't serf do this for us? */
+ serf_bucket_headers_setn(hdrs_bkt, "Accept-Encoding", "gzip");
+
+ if (ctx->authn != NULL) {
+ serf_bucket_headers_setn(hdrs_bkt, "Authorization", ctx->authn);
+ }
+
+ *acceptor = ctx->acceptor;
+ *acceptor_baton = ctx->acceptor_baton;
+ *handler = ctx->handler;
+ *handler_baton = ctx;
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t setup_bwtp_upgrade(serf_request_t *request,
+ void *setup_baton,
+ serf_bucket_t **req_bkt,
+ serf_response_acceptor_t *acceptor,
+ void **acceptor_baton,
+ serf_response_handler_t *handler,
+ void **handler_baton,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *hdrs_bkt;
+ handler_baton_t *ctx = setup_baton;
+
+ *req_bkt = serf_bucket_request_create("OPTIONS", "*", NULL,
+ serf_request_get_alloc(request));
+
+ hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);
+
+ serf_bucket_headers_setn(hdrs_bkt, "Upgrade", "BWTP/1.0");
+ serf_bucket_headers_setn(hdrs_bkt, "Connection", "Upgrade");
+
+ *acceptor = ctx->acceptor;
+ *acceptor_baton = ctx->acceptor_baton;
+ *handler = handle_bwtp_upgrade;
+ *handler_baton = ctx;
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t setup_channel(serf_request_t *request,
+ void *setup_baton,
+ serf_bucket_t **req_bkt,
+ serf_response_acceptor_t *acceptor,
+ void **acceptor_baton,
+ serf_response_handler_t *handler,
+ void **handler_baton,
+ apr_pool_t *pool)
+{
+ handler_baton_t *ctx = setup_baton;
+ serf_bucket_t *hdrs_bkt;
+
+ *req_bkt = serf_bucket_bwtp_channel_open(0, ctx->path,
+ serf_request_get_alloc(request));
+
+ hdrs_bkt = serf_bucket_bwtp_frame_get_headers(*req_bkt);
+
+ /* FIXME: Shouldn't we be able to figure out the host ourselves? */
+ serf_bucket_headers_setn(hdrs_bkt, "Host", ctx->host);
+ serf_bucket_headers_setn(hdrs_bkt, "User-Agent",
+ "Serf/" SERF_VERSION_STRING);
+ /* Shouldn't serf do this for us? */
+ serf_bucket_headers_setn(hdrs_bkt, "Accept-Encoding", "gzip");
+
+ if (ctx->authn != NULL) {
+ serf_bucket_headers_setn(hdrs_bkt, "Authorization", ctx->authn);
+ }
+
+ *acceptor = ctx->acceptor;
+ *acceptor_baton = ctx->acceptor_baton;
+ *handler = ctx->handler;
+ *handler_baton = ctx;
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t setup_close(serf_request_t *request,
+ void *setup_baton,
+ serf_bucket_t **req_bkt,
+ serf_response_acceptor_t *acceptor,
+ void **acceptor_baton,
+ serf_response_handler_t *handler,
+ void **handler_baton,
+ apr_pool_t *pool)
+{
+ handler_baton_t *ctx = setup_baton;
+
+ *req_bkt = serf_bucket_bwtp_channel_close(0, serf_request_get_alloc(request));
+
+ *acceptor = ctx->acceptor;
+ *acceptor_baton = ctx->acceptor_baton;
+ *handler = ctx->handler;
+ *handler_baton = ctx;
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t handle_bwtp(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ const char *data;
+ apr_size_t len;
+ apr_status_t status;
+
+ if (!response) {
+ /* A NULL response can come back if the request failed completely */
+ return APR_EGENERAL;
+ }
+ status = serf_bucket_bwtp_incoming_frame_wait_for_headers(response);
+ if (SERF_BUCKET_READ_ERROR(status) || APR_STATUS_IS_EAGAIN(status)) {
+ return status;
+ }
+ printf("BWTP %p frame: %d %d %s\n",
+ response, serf_bucket_bwtp_frame_get_channel(response),
+ serf_bucket_bwtp_frame_get_type(response),
+ serf_bucket_bwtp_frame_get_phrase(response));
+
+
+ while (1) {
+ status = serf_bucket_read(response, 2048, &data, &len);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return status;
+
+ /* got some data. print it out. */
+ if (len) {
+ puts("BWTP body:\n---");
+ fwrite(data, 1, len, stdout);
+ puts("\n---");
+ }
+
+ /* are we done yet? */
+ if (APR_STATUS_IS_EOF(status)) {
+ return APR_EOF;
+ }
+
+ /* have we drained the response so far? */
+ if (APR_STATUS_IS_EAGAIN(status))
+ return status;
+
+ /* loop to read some more. */
+ }
+ /* NOTREACHED */
+}
+
+static apr_status_t handle_bwtp_upgrade(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ const char *data;
+ apr_size_t len;
+ serf_status_line sl;
+ apr_status_t status;
+ handler_baton_t *ctx = handler_baton;
+
+ if (!response) {
+ /* A NULL response can come back if the request failed completely */
+ return APR_EGENERAL;
+ }
+ status = serf_bucket_response_status(response, &sl);
+ if (status) {
+ return status;
+ }
+
+ while (1) {
+ status = serf_bucket_read(response, 2048, &data, &len);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return status;
+
+ /* got some data. print it out. */
+ fwrite(data, 1, len, stdout);
+
+ /* are we done yet? */
+ if (APR_STATUS_IS_EOF(status)) {
+ int i;
+ serf_connection_t *conn;
+ serf_request_t *new_req;
+
+ conn = serf_request_get_conn(request);
+
+ serf_connection_set_async_responses(conn,
+ accept_bwtp, ctx->acceptor_baton, handle_bwtp, NULL);
+
+ new_req = serf_connection_request_create(conn, setup_channel, ctx);
+ for (i = 0; i < ctx->acceptor_baton->count; i++) {
+ new_req = serf_connection_request_create(conn,
+ setup_request,
+ ctx);
+ }
+
+ return APR_EOF;
+ }
+
+ /* have we drained the response so far? */
+ if (APR_STATUS_IS_EAGAIN(status))
+ return status;
+
+ /* loop to read some more. */
+ }
+ /* NOTREACHED */
+}
+
+static apr_status_t handle_response(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ const char *data;
+ apr_size_t len;
+ serf_status_line sl;
+ apr_status_t status;
+ handler_baton_t *ctx = handler_baton;
+
+ if (!response) {
+ /* A NULL response can come back if the request failed completely */
+ return APR_EGENERAL;
+ }
+ status = serf_bucket_response_status(response, &sl);
+ if (status) {
+ return status;
+ }
+
+ while (1) {
+ status = serf_bucket_read(response, 2048, &data, &len);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return status;
+
+ /* got some data. print it out. */
+ fwrite(data, 1, len, stdout);
+
+ /* are we done yet? */
+ if (APR_STATUS_IS_EOF(status)) {
+ if (ctx->print_headers) {
+ serf_bucket_t *hdrs;
+ hdrs = serf_bucket_response_get_headers(response);
+ while (1) {
+ status = serf_bucket_read(hdrs, 2048, &data, &len);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return status;
+
+ fwrite(data, 1, len, stdout);
+ if (APR_STATUS_IS_EOF(status)) {
+ break;
+ }
+ }
+ }
+
+ apr_atomic_dec32(&ctx->requests_outstanding);
+ if (!ctx->requests_outstanding) {
+ serf_connection_t *conn;
+ serf_request_t *new_req;
+
+ conn = serf_request_get_conn(request);
+ new_req =
+ serf_connection_request_create(conn, setup_close, ctx);
+ }
+ return APR_EOF;
+ }
+
+ /* have we drained the response so far? */
+ if (APR_STATUS_IS_EAGAIN(status))
+ return status;
+
+ /* loop to read some more. */
+ }
+ /* NOTREACHED */
+}
+
+static void print_usage(apr_pool_t *pool)
+{
+ puts("serf_get [options] URL");
+ puts("-h\tDisplay this help");
+ puts("-v\tDisplay version");
+ puts("-H\tPrint response headers");
+ puts("-n <count> Fetch URL <count> times");
+ puts("-a <user:password> Present Basic authentication credentials");
+ puts("-m <method> Use the <method> HTTP Method");
+ puts("-f <file> Use the <file> as the request body");
+}
+
+int main(int argc, const char **argv)
+{
+ apr_status_t status;
+ apr_pool_t *pool;
+ apr_sockaddr_t *address;
+ serf_context_t *context;
+ serf_connection_t *connection;
+ serf_request_t *request;
+ app_baton_t app_ctx;
+ handler_baton_t handler_ctx;
+ apr_uri_t url;
+ const char *raw_url, *method, *req_body_path = NULL;
+ int i;
+ int print_headers;
+ char *authn = NULL;
+ apr_getopt_t *opt;
+ char opt_c;
+ const char *opt_arg;
+
+ apr_initialize();
+ atexit(apr_terminate);
+
+ apr_pool_create(&pool, NULL);
+ /* serf_initialize(); */
+
+ /* Default to one round of fetching. */
+ app_ctx.count = 1;
+ /* Default to GET. */
+ method = "GET";
+ /* Do not print headers by default. */
+ print_headers = 0;
+
+ apr_getopt_init(&opt, pool, argc, argv);
+
+ while ((status = apr_getopt(opt, "a:f:hHm:n:v", &opt_c, &opt_arg)) ==
+ APR_SUCCESS) {
+ int srclen, enclen;
+
+ switch (opt_c) {
+ case 'a':
+ srclen = strlen(opt_arg);
+ enclen = apr_base64_encode_len(srclen);
+ authn = apr_palloc(pool, enclen + 6);
+ strcpy(authn, "Basic ");
+ (void) apr_base64_encode(&authn[6], opt_arg, srclen);
+ break;
+ case 'f':
+ req_body_path = opt_arg;
+ break;
+ case 'h':
+ print_usage(pool);
+ exit(0);
+ break;
+ case 'H':
+ print_headers = 1;
+ break;
+ case 'm':
+ method = opt_arg;
+ break;
+ case 'n':
+ errno = 0;
+ app_ctx.count = apr_strtoi64(opt_arg, NULL, 10);
+ if (errno) {
+ printf("Problem converting number of times to fetch URL (%d)\n",
+ errno);
+ return errno;
+ }
+ break;
+ case 'v':
+ puts("Serf version: " SERF_VERSION_STRING);
+ exit(0);
+ default:
+ break;
+ }
+ }
+
+ if (opt->ind != opt->argc - 1) {
+ print_usage(pool);
+ exit(-1);
+ }
+
+ raw_url = argv[opt->ind];
+
+ apr_uri_parse(pool, raw_url, &url);
+ if (!url.port) {
+ url.port = apr_uri_port_of_scheme(url.scheme);
+ }
+ if (!url.path) {
+ url.path = "/";
+ }
+
+ if (strcasecmp(url.scheme, "https") == 0) {
+ app_ctx.using_ssl = 1;
+ }
+ else {
+ app_ctx.using_ssl = 0;
+ }
+
+ status = apr_sockaddr_info_get(&address,
+ url.hostname, APR_UNSPEC, url.port, 0,
+ pool);
+ if (status) {
+ printf("Error creating address: %d\n", status);
+ apr_pool_destroy(pool);
+ exit(1);
+ }
+
+ context = serf_context_create(pool);
+
+ /* ### Connection or Context should have an allocator? */
+ app_ctx.bkt_alloc = serf_bucket_allocator_create(pool, NULL, NULL);
+ app_ctx.ssl_ctx = NULL;
+
+ connection = serf_connection_create(context, address,
+ conn_setup, &app_ctx,
+ closed_connection, &app_ctx,
+ pool);
+
+ handler_ctx.requests_outstanding = 0;
+ handler_ctx.print_headers = print_headers;
+
+ handler_ctx.host = url.hostinfo;
+ handler_ctx.method = method;
+ handler_ctx.path = url.path;
+ handler_ctx.authn = authn;
+
+ handler_ctx.req_body_path = req_body_path;
+
+ handler_ctx.acceptor = accept_response;
+ handler_ctx.acceptor_baton = &app_ctx;
+ handler_ctx.handler = handle_response;
+
+ request = serf_connection_request_create(connection, setup_bwtp_upgrade,
+ &handler_ctx);
+
+ for (i = 0; i < app_ctx.count; i++) {
+ apr_atomic_inc32(&handler_ctx.requests_outstanding);
+ }
+
+ while (1) {
+ status = serf_context_run(context, SERF_DURATION_FOREVER, pool);
+ if (APR_STATUS_IS_TIMEUP(status))
+ continue;
+ if (status) {
+ char buf[200];
+
+ printf("Error running context: (%d) %s\n", status,
+ apr_strerror(status, buf, sizeof(buf)));
+ apr_pool_destroy(pool);
+ exit(1);
+ }
+ if (!apr_atomic_read32(&handler_ctx.requests_outstanding)) {
+ break;
+ }
+ /* Debugging purposes only! */
+ serf_debug__closed_conn(app_ctx.bkt_alloc);
+ }
+
+ serf_connection_close(connection);
+
+ apr_pool_destroy(pool);
+ return 0;
+}
diff --git a/test/serf_get.c b/test/serf_get.c
new file mode 100644
index 0000000..ac1bb8e
--- /dev/null
+++ b/test/serf_get.c
@@ -0,0 +1,503 @@
+/* Copyright 2002-2004 Justin Erenkrantz and Greg Stein
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+
+#include <apr.h>
+#include <apr_uri.h>
+#include <apr_strings.h>
+#include <apr_atomic.h>
+#include <apr_base64.h>
+#include <apr_getopt.h>
+#include <apr_version.h>
+
+#include "serf.h"
+
+typedef struct {
+ const char *hostinfo;
+ int using_ssl;
+ serf_ssl_context_t *ssl_ctx;
+ serf_bucket_alloc_t *bkt_alloc;
+} app_baton_t;
+
+static void closed_connection(serf_connection_t *conn,
+ void *closed_baton,
+ apr_status_t why,
+ apr_pool_t *pool)
+{
+ if (why) {
+ abort();
+ }
+}
+
+static void print_ssl_cert_errors(int failures)
+{
+ if (failures) {
+ fprintf(stderr, "INVALID CERTIFICATE:\n");
+ if (failures & SERF_SSL_CERT_NOTYETVALID)
+ fprintf(stderr, "* The certificate is not yet valid.\n");
+ if (failures & SERF_SSL_CERT_EXPIRED)
+ fprintf(stderr, "* The certificate expired.\n");
+ if (failures & SERF_SSL_CERT_SELF_SIGNED)
+ fprintf(stderr, "* The certificate is self-signed.\n");
+ if (failures & SERF_SSL_CERT_UNKNOWNCA)
+ fprintf(stderr, "* The CA is unknown.\n");
+ if (failures & SERF_SSL_CERT_UNKNOWN_FAILURE)
+ fprintf(stderr, "* Unknown failure.\n");
+ }
+}
+
+static apr_status_t ignore_all_cert_errors(void *data, int failures,
+ const serf_ssl_certificate_t *cert)
+{
+ print_ssl_cert_errors(failures);
+
+ /* In a real application, you would normally would not want to do this */
+ return APR_SUCCESS;
+}
+
+static apr_status_t print_certs(void *data, int failures, int error_depth,
+ const serf_ssl_certificate_t * const * certs,
+ apr_size_t certs_len)
+{
+ apr_pool_t *pool;
+ const serf_ssl_certificate_t *current;
+
+ apr_pool_create(&pool, NULL);
+
+ fprintf(stderr, "Received certificate chain with length %d\n",
+ (int)certs_len);
+ print_ssl_cert_errors(failures);
+ if (failures)
+ fprintf(stderr, "Error at depth=%d\n", error_depth);
+ else
+ fprintf(stderr, "Chain provided with depth=%d\n", error_depth);
+
+ while ((current = *certs) != NULL)
+ {
+ fprintf(stderr, "\n-----BEGIN CERTIFICATE-----\n");
+ fprintf(stderr, "%s\n", serf_ssl_cert_export(current, pool));
+ fprintf(stderr, "-----END CERTIFICATE-----\n");
+ ++certs;
+ }
+
+ apr_pool_destroy(pool);
+ return APR_SUCCESS;
+}
+
+static apr_status_t conn_setup(apr_socket_t *skt,
+ serf_bucket_t **input_bkt,
+ serf_bucket_t **output_bkt,
+ void *setup_baton,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *c;
+ app_baton_t *ctx = setup_baton;
+
+ c = serf_bucket_socket_create(skt, ctx->bkt_alloc);
+ if (ctx->using_ssl) {
+ c = serf_bucket_ssl_decrypt_create(c, ctx->ssl_ctx, ctx->bkt_alloc);
+ if (!ctx->ssl_ctx) {
+ ctx->ssl_ctx = serf_bucket_ssl_decrypt_context_get(c);
+ }
+ serf_ssl_server_cert_chain_callback_set(ctx->ssl_ctx,
+ ignore_all_cert_errors,
+ print_certs, NULL);
+ serf_ssl_set_hostname(ctx->ssl_ctx, ctx->hostinfo);
+
+ *output_bkt = serf_bucket_ssl_encrypt_create(*output_bkt, ctx->ssl_ctx,
+ ctx->bkt_alloc);
+ }
+
+ *input_bkt = c;
+
+ return APR_SUCCESS;
+}
+
+static serf_bucket_t* accept_response(serf_request_t *request,
+ serf_bucket_t *stream,
+ void *acceptor_baton,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *c;
+ serf_bucket_alloc_t *bkt_alloc;
+
+ /* get the per-request bucket allocator */
+ bkt_alloc = serf_request_get_alloc(request);
+
+ /* Create a barrier so the response doesn't eat us! */
+ c = serf_bucket_barrier_create(stream, bkt_alloc);
+
+ return serf_bucket_response_create(c, bkt_alloc);
+}
+
+typedef struct {
+#if APR_MAJOR_VERSION > 0
+ apr_uint32_t completed_requests;
+#else
+ apr_atomic_t completed_requests;
+#endif
+ int print_headers;
+
+ serf_response_acceptor_t acceptor;
+ app_baton_t *acceptor_baton;
+
+ serf_response_handler_t handler;
+
+ const char *host;
+ const char *method;
+ const char *path;
+ const char *req_body_path;
+ const char *authn;
+} handler_baton_t;
+
+/* Kludges for APR 0.9 support. */
+#if APR_MAJOR_VERSION == 0
+#define apr_atomic_inc32 apr_atomic_inc
+#define apr_atomic_dec32 apr_atomic_dec
+#define apr_atomic_read32 apr_atomic_read
+#endif
+
+static apr_status_t handle_response(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ const char *data;
+ apr_size_t len;
+ serf_status_line sl;
+ apr_status_t status;
+ handler_baton_t *ctx = handler_baton;
+
+ if (!response) {
+ /* A NULL response can come back if the request failed completely */
+ return APR_EGENERAL;
+ }
+ status = serf_bucket_response_status(response, &sl);
+ if (status) {
+ return status;
+ }
+
+ while (1) {
+ status = serf_bucket_read(response, 2048, &data, &len);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return status;
+
+ /* got some data. print it out. */
+ fwrite(data, 1, len, stdout);
+
+ /* are we done yet? */
+ if (APR_STATUS_IS_EOF(status)) {
+ if (ctx->print_headers) {
+ serf_bucket_t *hdrs;
+ hdrs = serf_bucket_response_get_headers(response);
+ while (1) {
+ status = serf_bucket_read(hdrs, 2048, &data, &len);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return status;
+
+ fwrite(data, 1, len, stdout);
+ if (APR_STATUS_IS_EOF(status)) {
+ break;
+ }
+ }
+ }
+
+ apr_atomic_inc32(&ctx->completed_requests);
+ return APR_EOF;
+ }
+
+ /* have we drained the response so far? */
+ if (APR_STATUS_IS_EAGAIN(status))
+ return status;
+
+ /* loop to read some more. */
+ }
+ /* NOTREACHED */
+}
+
+static apr_status_t setup_request(serf_request_t *request,
+ void *setup_baton,
+ serf_bucket_t **req_bkt,
+ serf_response_acceptor_t *acceptor,
+ void **acceptor_baton,
+ serf_response_handler_t *handler,
+ void **handler_baton,
+ apr_pool_t *pool)
+{
+ handler_baton_t *ctx = setup_baton;
+ serf_bucket_t *hdrs_bkt;
+ serf_bucket_t *body_bkt;
+
+ if (ctx->req_body_path) {
+ apr_file_t *file;
+ apr_status_t status;
+
+ status = apr_file_open(&file, ctx->req_body_path, APR_READ,
+ APR_OS_DEFAULT, pool);
+
+ if (status) {
+ printf("Error opening file (%s)\n", ctx->req_body_path);
+ return status;
+ }
+
+ body_bkt = serf_bucket_file_create(file,
+ serf_request_get_alloc(request));
+ }
+ else {
+ body_bkt = NULL;
+ }
+
+ *req_bkt = serf_request_bucket_request_create(request, ctx->method,
+ ctx->path, body_bkt,
+ serf_request_get_alloc(request));
+
+ hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);
+
+ serf_bucket_headers_setn(hdrs_bkt, "User-Agent",
+ "Serf/" SERF_VERSION_STRING);
+ /* Shouldn't serf do this for us? */
+ serf_bucket_headers_setn(hdrs_bkt, "Accept-Encoding", "gzip");
+
+ if (ctx->authn != NULL) {
+ serf_bucket_headers_setn(hdrs_bkt, "Authorization", ctx->authn);
+ }
+
+ *acceptor = ctx->acceptor;
+ *acceptor_baton = ctx->acceptor_baton;
+ *handler = ctx->handler;
+ *handler_baton = ctx;
+
+ return APR_SUCCESS;
+}
+
+static void print_usage(apr_pool_t *pool)
+{
+ puts("serf_get [options] URL");
+ puts("-h\tDisplay this help");
+ puts("-v\tDisplay version");
+ puts("-H\tPrint response headers");
+ puts("-n <count> Fetch URL <count> times");
+ puts("-a <user:password> Present Basic authentication credentials");
+ puts("-m <method> Use the <method> HTTP Method");
+ puts("-f <file> Use the <file> as the request body");
+ puts("-p <hostname:port> Use the <host:port> as proxy server");
+}
+
+int main(int argc, const char **argv)
+{
+ apr_status_t status;
+ apr_pool_t *pool;
+ serf_context_t *context;
+ serf_connection_t *connection;
+ serf_request_t *request;
+ app_baton_t app_ctx;
+ handler_baton_t handler_ctx;
+ apr_uri_t url;
+ const char *proxy = NULL;
+ const char *raw_url, *method, *req_body_path = NULL;
+ int count;
+ int i;
+ int print_headers;
+ char *authn = NULL;
+ apr_getopt_t *opt;
+ char opt_c;
+ const char *opt_arg;
+
+ apr_initialize();
+ atexit(apr_terminate);
+
+ apr_pool_create(&pool, NULL);
+ /* serf_initialize(); */
+
+ /* Default to one round of fetching. */
+ count = 1;
+ /* Default to GET. */
+ method = "GET";
+ /* Do not print headers by default. */
+ print_headers = 0;
+
+ apr_getopt_init(&opt, pool, argc, argv);
+
+ while ((status = apr_getopt(opt, "a:f:hHm:n:vp:", &opt_c, &opt_arg)) ==
+ APR_SUCCESS) {
+ int srclen, enclen;
+
+ switch (opt_c) {
+ case 'a':
+ srclen = strlen(opt_arg);
+ enclen = apr_base64_encode_len(srclen);
+ authn = apr_palloc(pool, enclen + 6);
+ strcpy(authn, "Basic ");
+ (void) apr_base64_encode(&authn[6], opt_arg, srclen);
+ break;
+ case 'f':
+ req_body_path = opt_arg;
+ break;
+ case 'h':
+ print_usage(pool);
+ exit(0);
+ break;
+ case 'H':
+ print_headers = 1;
+ break;
+ case 'm':
+ method = opt_arg;
+ break;
+ case 'n':
+ errno = 0;
+ count = apr_strtoi64(opt_arg, NULL, 10);
+ if (errno) {
+ printf("Problem converting number of times to fetch URL (%d)\n",
+ errno);
+ return errno;
+ }
+ break;
+ case 'p':
+ proxy = opt_arg;
+ break;
+ case 'v':
+ puts("Serf version: " SERF_VERSION_STRING);
+ exit(0);
+ default:
+ break;
+ }
+ }
+
+ if (opt->ind != opt->argc - 1) {
+ print_usage(pool);
+ exit(-1);
+ }
+
+ raw_url = argv[opt->ind];
+
+ apr_uri_parse(pool, raw_url, &url);
+ if (!url.port) {
+ url.port = apr_uri_port_of_scheme(url.scheme);
+ }
+ if (!url.path) {
+ url.path = "/";
+ }
+
+ if (strcasecmp(url.scheme, "https") == 0) {
+ app_ctx.using_ssl = 1;
+ }
+ else {
+ app_ctx.using_ssl = 0;
+ }
+
+ app_ctx.hostinfo = url.hostinfo;
+
+ context = serf_context_create(pool);
+
+ if (proxy)
+ {
+ apr_sockaddr_t *proxy_address = NULL;
+ apr_port_t proxy_port;
+ char *proxy_host;
+ char *proxy_scope;
+
+ status = apr_parse_addr_port(&proxy_host, &proxy_scope, &proxy_port, proxy, pool);
+ if (status)
+ {
+ printf("Cannot parse proxy hostname/port: %d\n", status);
+ apr_pool_destroy(pool);
+ exit(1);
+ }
+
+ if (!proxy_host)
+ {
+ printf("Proxy hostname must be specified\n");
+ apr_pool_destroy(pool);
+ exit(1);
+ }
+
+ if (!proxy_port)
+ {
+ printf("Proxy port must be specified\n");
+ apr_pool_destroy(pool);
+ exit(1);
+ }
+
+ status = apr_sockaddr_info_get(&proxy_address, proxy_host, APR_UNSPEC,
+ proxy_port, 0, pool);
+
+ if (status)
+ {
+ printf("Cannot resolve proxy address '%s': %d\n", proxy_host, status);
+ apr_pool_destroy(pool);
+ exit(1);
+ }
+
+ serf_config_proxy(context, proxy_address);
+ }
+
+ /* ### Connection or Context should have an allocator? */
+ app_ctx.bkt_alloc = serf_bucket_allocator_create(pool, NULL, NULL);
+ app_ctx.ssl_ctx = NULL;
+
+ status = serf_connection_create2(&connection, context, url,
+ conn_setup, &app_ctx,
+ closed_connection, &app_ctx,
+ pool);
+ if (status) {
+ printf("Error creating connection: %d\n", status);
+ apr_pool_destroy(pool);
+ exit(1);
+ }
+
+ handler_ctx.completed_requests = 0;
+ handler_ctx.print_headers = print_headers;
+
+ handler_ctx.host = url.hostinfo;
+ handler_ctx.method = method;
+ handler_ctx.path = url.path;
+ handler_ctx.authn = authn;
+
+ handler_ctx.req_body_path = req_body_path;
+
+ handler_ctx.acceptor = accept_response;
+ handler_ctx.acceptor_baton = &app_ctx;
+ handler_ctx.handler = handle_response;
+
+ for (i = 0; i < count; i++) {
+ request = serf_connection_request_create(connection, setup_request,
+ &handler_ctx);
+ }
+
+ while (1) {
+ status = serf_context_run(context, SERF_DURATION_FOREVER, pool);
+ if (APR_STATUS_IS_TIMEUP(status))
+ continue;
+ if (status) {
+ char buf[200];
+
+ printf("Error running context: (%d) %s\n", status,
+ apr_strerror(status, buf, sizeof(buf)));
+ apr_pool_destroy(pool);
+ exit(1);
+ }
+ if (apr_atomic_read32(&handler_ctx.completed_requests) >= count) {
+ break;
+ }
+ /* Debugging purposes only! */
+ serf_debug__closed_conn(app_ctx.bkt_alloc);
+ }
+
+ serf_connection_close(connection);
+
+ apr_pool_destroy(pool);
+ return 0;
+}
diff --git a/test/serf_request.c b/test/serf_request.c
new file mode 100644
index 0000000..0bd6a11
--- /dev/null
+++ b/test/serf_request.c
@@ -0,0 +1,79 @@
+/* Copyright 2002-2004 Justin Erenkrantz and Greg Stein
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+
+#include "serf.h"
+
+static apr_status_t drain_bucket(serf_bucket_t *bucket)
+{
+ apr_status_t status;
+ const char *data;
+ apr_size_t len;
+
+ while (1) {
+ status = serf_bucket_read(bucket, 2048, &data, &len);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return status;
+
+ /* got some data. print it out. */
+ fwrite(data, 1, len, stdout);
+
+ /* are we done yet? */
+ if (APR_STATUS_IS_EOF(status)) {
+ return APR_EOF;
+ }
+
+ /* have we drained the response so far? */
+ if (APR_STATUS_IS_EAGAIN(status))
+ return APR_SUCCESS;
+
+ /* loop to read some more. */
+ }
+ /* NOTREACHED */
+}
+
+int main(int argc, const char **argv)
+{
+ apr_pool_t *pool;
+ serf_bucket_t *req_bkt;
+ serf_bucket_t *hdrs_bkt;
+ serf_bucket_alloc_t *allocator;
+
+ apr_initialize();
+ atexit(apr_terminate);
+
+ apr_pool_create(&pool, NULL);
+ /* serf_initialize(); */
+
+ allocator = serf_bucket_allocator_create(pool, NULL, NULL);
+
+ req_bkt = serf_bucket_request_create("GET", "/", NULL, allocator);
+
+ hdrs_bkt = serf_bucket_request_get_headers(req_bkt);
+
+ /* FIXME: Shouldn't we be able to figure out the host ourselves? */
+ serf_bucket_headers_setn(hdrs_bkt, "Host", "localhost");
+ serf_bucket_headers_setn(hdrs_bkt, "User-Agent",
+ "Serf/" SERF_VERSION_STRING);
+
+ (void) drain_bucket(req_bkt);
+
+ serf_bucket_destroy(req_bkt);
+
+ apr_pool_destroy(pool);
+
+ return 0;
+}
diff --git a/test/serf_response.c b/test/serf_response.c
new file mode 100644
index 0000000..a8d7d85
--- /dev/null
+++ b/test/serf_response.c
@@ -0,0 +1,161 @@
+/* Copyright 2002-2004 Justin Erenkrantz and Greg Stein
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+
+#include <apr.h>
+#include <apr_uri.h>
+#include <apr_strings.h>
+#include <apr_atomic.h>
+#include <apr_version.h>
+
+#include "serf.h"
+
+typedef struct {
+ const char *resp_file;
+ serf_bucket_t *bkt;
+} accept_baton_t;
+
+static serf_bucket_t* accept_response(void *acceptor_baton,
+ serf_bucket_alloc_t *bkt_alloc,
+ apr_pool_t *pool)
+{
+ accept_baton_t *ctx = acceptor_baton;
+ serf_bucket_t *c;
+ apr_file_t *file;
+ apr_status_t status;
+
+ status = apr_file_open(&file, ctx->resp_file,
+ APR_READ, APR_OS_DEFAULT, pool);
+ if (status) {
+ return NULL;
+ }
+
+ c = ctx->bkt = serf_bucket_file_create(file, bkt_alloc);
+
+ c = serf_bucket_barrier_create(c, bkt_alloc);
+
+ return serf_bucket_response_create(c, bkt_alloc);
+}
+
+typedef struct {
+#if APR_MAJOR_VERSION > 0
+ apr_uint32_t requests_outstanding;
+#else
+ apr_atomic_t requests_outstanding;
+#endif
+} handler_baton_t;
+
+/* Kludges for APR 0.9 support. */
+#if APR_MAJOR_VERSION == 0
+#define apr_atomic_inc32 apr_atomic_inc
+#define apr_atomic_dec32 apr_atomic_dec
+#define apr_atomic_read32 apr_atomic_read
+#endif
+
+static apr_status_t handle_response(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ const char *data, *s;
+ apr_size_t len;
+ serf_status_line sl;
+ apr_status_t status;
+ handler_baton_t *ctx = handler_baton;
+
+ status = serf_bucket_response_status(response, &sl);
+ if (status) {
+ if (APR_STATUS_IS_EAGAIN(status)) {
+ return APR_SUCCESS;
+ }
+ abort();
+ }
+
+ status = serf_bucket_read(response, 2048, &data, &len);
+
+ if (!status || APR_STATUS_IS_EOF(status)) {
+ if (len) {
+ s = apr_pstrmemdup(pool, data, len);
+ printf("%s", s);
+ }
+ }
+ else if (APR_STATUS_IS_EAGAIN(status)) {
+ status = APR_SUCCESS;
+ }
+ if (APR_STATUS_IS_EOF(status)) {
+ serf_bucket_t *hdrs;
+ const char *v;
+
+ hdrs = serf_bucket_response_get_headers(response);
+ v = serf_bucket_headers_get(hdrs, "Trailer-Test");
+ if (v) {
+ printf("Trailer-Test: %s\n", v);
+ }
+
+ apr_atomic_dec32(&ctx->requests_outstanding);
+ }
+
+ return status;
+}
+
+int main(int argc, const char **argv)
+{
+ apr_status_t status;
+ apr_pool_t *pool;
+ serf_bucket_t *resp_bkt;
+ accept_baton_t accept_ctx;
+ handler_baton_t handler_ctx;
+ serf_bucket_alloc_t *allocator;
+
+ if (argc != 2) {
+ printf("%s: [Resp. File]\n", argv[0]);
+ exit(-1);
+ }
+ accept_ctx.resp_file = argv[1];
+ accept_ctx.bkt = NULL;
+
+ apr_initialize();
+ atexit(apr_terminate);
+
+ apr_pool_create(&pool, NULL);
+ apr_atomic_init(pool);
+ /* serf_initialize(); */
+
+ allocator = serf_bucket_allocator_create(pool, NULL, NULL);
+
+ handler_ctx.requests_outstanding = 0;
+ apr_atomic_inc32(&handler_ctx.requests_outstanding);
+
+ resp_bkt = accept_response(&accept_ctx, allocator, pool);
+ while (1) {
+ status = handle_response(NULL, resp_bkt, &handler_ctx, pool);
+ if (APR_STATUS_IS_TIMEUP(status))
+ continue;
+ if (SERF_BUCKET_READ_ERROR(status)) {
+ printf("Error running context: %d\n", status);
+ exit(1);
+ }
+ if (!apr_atomic_read32(&handler_ctx.requests_outstanding)) {
+ break;
+ }
+ }
+ serf_bucket_destroy(resp_bkt);
+ serf_bucket_destroy(accept_ctx.bkt);
+
+ apr_pool_destroy(pool);
+
+ return 0;
+}
diff --git a/test/serf_server.c b/test/serf_server.c
new file mode 100644
index 0000000..4d8a0c6
--- /dev/null
+++ b/test/serf_server.c
@@ -0,0 +1,147 @@
+/* Copyright 2002-2004 Justin Erenkrantz and Greg Stein
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+
+#include <apr.h>
+#include <apr_uri.h>
+#include <apr_strings.h>
+#include <apr_atomic.h>
+#include <apr_base64.h>
+#include <apr_getopt.h>
+#include <apr_version.h>
+
+#include "serf.h"
+
+typedef struct {
+ int foo;
+} app_baton_t;
+
+
+static apr_status_t incoming_request(serf_context_t *ctx,
+ serf_incoming_request_t *req,
+ void *request_baton,
+ apr_pool_t *pool)
+{
+ printf("INCOMING REQUEST\n");
+ return APR_SUCCESS;
+}
+
+
+static apr_status_t accept_fn(serf_context_t *ctx,
+ serf_listener_t *l,
+ void *baton,
+ apr_socket_t *insock,
+ apr_pool_t *pool)
+{
+ serf_incoming_t *client = NULL;
+ printf("new connection from \n");
+ return serf_incoming_create(&client, ctx, insock, baton, incoming_request, pool);
+}
+
+static void print_usage(apr_pool_t *pool)
+{
+ puts("serf_server [options] listen_address:listen_port");
+ puts("-h\tDisplay this help");
+ puts("-v\tDisplay version");
+}
+
+int main(int argc, const char **argv)
+{
+ apr_status_t rv;
+ apr_pool_t *pool;
+ serf_context_t *context;
+ serf_listener_t *listener;
+ app_baton_t app_ctx;
+ const char *listen_spec;
+ char *addr = NULL;
+ char *scope_id = NULL;
+ apr_port_t port;
+ apr_getopt_t *opt;
+ char opt_c;
+ const char *opt_arg;
+
+ apr_initialize();
+ atexit(apr_terminate);
+
+ apr_pool_create(&pool, NULL);
+
+ apr_getopt_init(&opt, pool, argc, argv);
+
+ while ((rv = apr_getopt(opt, "hv", &opt_c, &opt_arg)) ==
+ APR_SUCCESS) {
+ switch (opt_c) {
+ case 'h':
+ print_usage(pool);
+ exit(0);
+ break;
+ case 'v':
+ puts("Serf version: " SERF_VERSION_STRING);
+ exit(0);
+ default:
+ break;
+ }
+ }
+
+ if (opt->ind != opt->argc - 1) {
+ print_usage(pool);
+ exit(-1);
+ }
+
+ listen_spec = argv[opt->ind];
+
+
+ rv = apr_parse_addr_port(&addr, &scope_id, &port, listen_spec, pool);
+ if (rv) {
+ printf("Error parsing listen address: %d\n", rv);
+ exit(1);
+ }
+
+ if (!addr) {
+ addr = "0.0.0.0";
+ }
+
+ if (port == 0) {
+ port = 8080;
+ }
+
+ context = serf_context_create(pool);
+
+ /* TODO.... stuff */
+ app_ctx.foo = 1;
+ rv = serf_listener_create(&listener, context, addr, port,
+ &app_ctx, accept_fn, pool);
+ if (rv) {
+ printf("Error parsing listener: %d\n", rv);
+ exit(1);
+ }
+
+ while (1) {
+ rv = serf_context_run(context, SERF_DURATION_FOREVER, pool);
+ if (APR_STATUS_IS_TIMEUP(rv))
+ continue;
+ if (rv) {
+ char buf[200];
+
+ printf("Error running context: (%d) %s\n", rv,
+ apr_strerror(rv, buf, sizeof(buf)));
+ apr_pool_destroy(pool);
+ exit(1);
+ }
+ }
+
+ apr_pool_destroy(pool);
+ return 0;
+}
diff --git a/test/serf_spider.c b/test/serf_spider.c
new file mode 100644
index 0000000..2945e98
--- /dev/null
+++ b/test/serf_spider.c
@@ -0,0 +1,826 @@
+/* Copyright 2002-2004 Justin Erenkrantz and Greg Stein
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+
+#include <apr.h>
+#include <apr_uri.h>
+#include <apr_strings.h>
+#include <apr_atomic.h>
+#include <apr_base64.h>
+#include <apr_getopt.h>
+#include <apr_xml.h>
+#include <apr_thread_proc.h>
+#include <apr_thread_mutex.h>
+#include <apr_thread_cond.h>
+#include <apr_version.h>
+
+#include "serf.h"
+#include "serf_bucket_util.h"
+
+/*#define SERF_VERBOSE*/
+
+#if !APR_HAS_THREADS
+#error serf spider needs threads.
+#endif
+
+/* This is a rough-sketch example of how a multi-threaded spider could be
+ * constructed using serf.
+ *
+ * A network thread will read in a URL and feed it into an expat parser.
+ * After the entire response is read, the XML structure and the path is
+ * passed to a set of parser threads. These threads will scan the document
+ * for HTML href's and queue up any links that it finds.
+ *
+ * It does try to stay on the same server as it only uses one connection.
+ *
+ * Because we feed the responses into an XML parser, the documents must be
+ * well-formed XHTML.
+ *
+ * There is no duplicate link detection. You've been warned.
+ */
+
+/* The structure passed to the parser thread after we've read the entire
+ * response.
+ */
+typedef struct {
+ apr_xml_doc *doc;
+ char *path;
+ apr_pool_t *pool;
+} doc_path_t;
+
+typedef struct {
+ const char *authn;
+ int using_ssl;
+ serf_ssl_context_t *ssl_ctx;
+ serf_bucket_alloc_t *bkt_alloc;
+} app_baton_t;
+
+static void closed_connection(serf_connection_t *conn,
+ void *closed_baton,
+ apr_status_t why,
+ apr_pool_t *pool)
+{
+ if (why) {
+ abort();
+ }
+}
+
+static apr_status_t conn_setup(apr_socket_t *skt,
+ serf_bucket_t **input_bkt,
+ serf_bucket_t **output_bkt,
+ void *setup_baton,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *c;
+ app_baton_t *ctx = setup_baton;
+
+ c = serf_bucket_socket_create(skt, ctx->bkt_alloc);
+ if (ctx->using_ssl) {
+ c = serf_bucket_ssl_decrypt_create(c, ctx->ssl_ctx, ctx->bkt_alloc);
+ }
+
+ *input_bkt = c;
+
+ return APR_SUCCESS;
+}
+
+static serf_bucket_t* accept_response(serf_request_t *request,
+ serf_bucket_t *stream,
+ void *acceptor_baton,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *c;
+ serf_bucket_alloc_t *bkt_alloc;
+
+ /* get the per-request bucket allocator */
+ bkt_alloc = serf_request_get_alloc(request);
+
+ /* Create a barrier so the response doesn't eat us! */
+ c = serf_bucket_barrier_create(stream, bkt_alloc);
+
+ return serf_bucket_response_create(c, bkt_alloc);
+}
+
+typedef struct {
+ serf_bucket_alloc_t *allocator;
+#if APR_MAJOR_VERSION > 0
+ apr_uint32_t *requests_outstanding;
+#else
+ apr_atomic_t *requests_outstanding;
+#endif
+ serf_bucket_alloc_t *doc_queue_alloc;
+ apr_array_header_t *doc_queue;
+ apr_thread_cond_t *doc_queue_condvar;
+
+ const char *hostinfo;
+
+ /* includes: path, query, fragment. */
+ char *full_path;
+ apr_size_t full_path_len;
+
+ char *path;
+ apr_size_t path_len;
+
+ char *query;
+ apr_size_t query_len;
+
+ char *fragment;
+ apr_size_t fragment_len;
+
+ apr_xml_parser *parser;
+ apr_pool_t *parser_pool;
+
+ int hdr_read;
+ int is_html;
+
+ serf_response_acceptor_t acceptor;
+ void *acceptor_baton;
+ serf_response_handler_t handler;
+
+ app_baton_t *app_ctx;
+} handler_baton_t;
+
+/* Kludges for APR 0.9 support. */
+#if APR_MAJOR_VERSION == 0
+#define apr_atomic_inc32 apr_atomic_inc
+#define apr_atomic_dec32 apr_atomic_dec
+#define apr_atomic_read32 apr_atomic_read
+#define apr_atomic_set32 apr_atomic_set
+#endif
+
+static apr_status_t handle_response(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ const char *data;
+ apr_size_t len;
+ serf_status_line sl;
+ apr_status_t status;
+ handler_baton_t *ctx = handler_baton;
+
+ if (!response) {
+ /* Oh no! We've been cancelled! */
+ abort();
+ }
+
+ status = serf_bucket_response_status(response, &sl);
+ if (status) {
+ if (APR_STATUS_IS_EAGAIN(status)) {
+ return APR_SUCCESS;
+ }
+ abort();
+ }
+
+ while (1) {
+ status = serf_bucket_read(response, 2048, &data, &len);
+
+ if (SERF_BUCKET_READ_ERROR(status))
+ return status;
+
+ /*fwrite(data, 1, len, stdout);*/
+
+ if (!ctx->hdr_read) {
+ serf_bucket_t *hdrs;
+ const char *val;
+
+ printf("Processing %s\n", ctx->path);
+
+ hdrs = serf_bucket_response_get_headers(response);
+ val = serf_bucket_headers_get(hdrs, "Content-Type");
+ /* FIXME: This check isn't quite right because Content-Type could
+ * be decorated; ideally strcasestr would be correct.
+ */
+ if (val && strcasecmp(val, "text/html") == 0) {
+ ctx->is_html = 1;
+ apr_pool_create(&ctx->parser_pool, NULL);
+ ctx->parser = apr_xml_parser_create(ctx->parser_pool);
+ }
+ else {
+ ctx->is_html = 0;
+ }
+ ctx->hdr_read = 1;
+ }
+ if (ctx->is_html) {
+ apr_status_t xs;
+
+ xs = apr_xml_parser_feed(ctx->parser, data, len);
+ /* Uh-oh. */
+ if (xs) {
+#ifdef SERF_VERBOSE
+ printf("XML parser error (feed): %d\n", xs);
+#endif
+ ctx->is_html = 0;
+ }
+ }
+
+ /* are we done yet? */
+ if (APR_STATUS_IS_EOF(status)) {
+
+ if (ctx->is_html) {
+ apr_xml_doc *xmld;
+ apr_status_t xs;
+ doc_path_t *dup;
+
+ xs = apr_xml_parser_done(ctx->parser, &xmld);
+ if (xs) {
+#ifdef SERF_VERBOSE
+ printf("XML parser error (done): %d\n", xs);
+#endif
+ return xs;
+ }
+ dup = (doc_path_t*)
+ serf_bucket_mem_alloc(ctx->doc_queue_alloc,
+ sizeof(doc_path_t));
+ dup->doc = xmld;
+ dup->path = (char*)serf_bucket_mem_alloc(ctx->doc_queue_alloc,
+ ctx->path_len);
+ memcpy(dup->path, ctx->path, ctx->path_len);
+ dup->pool = ctx->parser_pool;
+
+ *(doc_path_t **)apr_array_push(ctx->doc_queue) = dup;
+
+ apr_thread_cond_signal(ctx->doc_queue_condvar);
+ }
+
+ apr_atomic_dec32(ctx->requests_outstanding);
+ serf_bucket_mem_free(ctx->allocator, ctx->path);
+ if (ctx->query) {
+ serf_bucket_mem_free(ctx->allocator, ctx->query);
+ serf_bucket_mem_free(ctx->allocator, ctx->full_path);
+ }
+ if (ctx->fragment) {
+ serf_bucket_mem_free(ctx->allocator, ctx->fragment);
+ }
+ serf_bucket_mem_free(ctx->allocator, ctx);
+ return APR_EOF;
+ }
+
+ /* have we drained the response so far? */
+ if (APR_STATUS_IS_EAGAIN(status))
+ return APR_SUCCESS;
+
+ /* loop to read some more. */
+ }
+ /* NOTREACHED */
+}
+
+typedef struct {
+ apr_uint32_t *requests_outstanding;
+ serf_connection_t *connection;
+ apr_array_header_t *doc_queue;
+ serf_bucket_alloc_t *doc_queue_alloc;
+
+ apr_thread_cond_t *condvar;
+ apr_thread_mutex_t *mutex;
+
+ /* Master host: for now, we'll stick to one host. */
+ const char *hostinfo;
+
+ app_baton_t *app_ctx;
+} parser_baton_t;
+
+static apr_status_t setup_request(serf_request_t *request,
+ void *setup_baton,
+ serf_bucket_t **req_bkt,
+ serf_response_acceptor_t *acceptor,
+ void **acceptor_baton,
+ serf_response_handler_t *handler,
+ void **handler_baton,
+ apr_pool_t *pool)
+{
+ handler_baton_t *ctx = setup_baton;
+ serf_bucket_t *hdrs_bkt;
+
+ *req_bkt = serf_bucket_request_create("GET", ctx->full_path, NULL,
+ serf_request_get_alloc(request));
+
+ hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);
+
+ /* FIXME: Shouldn't we be able to figure out the host ourselves? */
+ serf_bucket_headers_setn(hdrs_bkt, "Host", ctx->hostinfo);
+ serf_bucket_headers_setn(hdrs_bkt, "User-Agent",
+ "Serf/" SERF_VERSION_STRING);
+
+ /* Shouldn't serf do this for us? */
+ serf_bucket_headers_setn(hdrs_bkt, "Accept-Encoding", "gzip");
+
+ if (ctx->app_ctx->authn != NULL) {
+ serf_bucket_headers_setn(hdrs_bkt, "Authorization",
+ ctx->app_ctx->authn);
+ }
+
+ if (ctx->app_ctx->using_ssl) {
+ serf_bucket_alloc_t *req_alloc;
+
+ req_alloc = serf_request_get_alloc(request);
+
+ if (ctx->app_ctx->ssl_ctx == NULL) {
+ *req_bkt = serf_bucket_ssl_encrypt_create(*req_bkt, NULL,
+ ctx->app_ctx->bkt_alloc);
+ ctx->app_ctx->ssl_ctx =
+ serf_bucket_ssl_encrypt_context_get(*req_bkt);
+ }
+ else {
+ *req_bkt =
+ serf_bucket_ssl_encrypt_create(*req_bkt, ctx->app_ctx->ssl_ctx,
+ ctx->app_ctx->bkt_alloc);
+ }
+ }
+
+
+#ifdef SERF_VERBOSE
+ printf("Url requesting: %s\n", ctx->full_path);
+#endif
+
+ *acceptor = ctx->acceptor;
+ *acceptor_baton = ctx->acceptor_baton;
+ *handler = ctx->handler;
+ *handler_baton = ctx;
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t create_request(const char *hostinfo,
+ const char *path,
+ const char *query,
+ const char *fragment,
+ parser_baton_t *ctx,
+ apr_pool_t *tmppool)
+{
+ handler_baton_t *new_ctx;
+
+ if (hostinfo) {
+ /* Yes, this is a pointer comparison; not a string comparison. */
+ if (hostinfo != ctx->hostinfo) {
+ /* Not on the same host; ignore */
+ return APR_SUCCESS;
+ }
+ }
+
+ new_ctx = (handler_baton_t*)serf_bucket_mem_alloc(ctx->app_ctx->bkt_alloc,
+ sizeof(handler_baton_t));
+ new_ctx->allocator = ctx->app_ctx->bkt_alloc;
+ new_ctx->requests_outstanding = ctx->requests_outstanding;
+ new_ctx->app_ctx = ctx->app_ctx;
+
+ /* See above: this example restricts ourselves to the same vhost. */
+ new_ctx->hostinfo = ctx->hostinfo;
+
+ /* we need to copy it so it falls under the request's scope. */
+ new_ctx->path_len = strlen(path);
+ new_ctx->path = (char*)serf_bucket_mem_alloc(ctx->app_ctx->bkt_alloc,
+ new_ctx->path_len + 1);
+ memcpy(new_ctx->path, path, new_ctx->path_len + 1);
+
+ /* we need to copy it so it falls under the request's scope. */
+ if (query) {
+ new_ctx->query_len = strlen(query);
+ new_ctx->query = (char*)serf_bucket_mem_alloc(ctx->app_ctx->bkt_alloc,
+ new_ctx->query_len + 1);
+ memcpy(new_ctx->query, query, new_ctx->query_len + 1);
+ }
+ else {
+ new_ctx->query = NULL;
+ new_ctx->query_len = 0;
+ }
+
+ /* we need to copy it so it falls under the request's scope. */
+ if (fragment) {
+ new_ctx->fragment_len = strlen(fragment);
+ new_ctx->fragment =
+ (char*)serf_bucket_mem_alloc(ctx->app_ctx->bkt_alloc,
+ new_ctx->fragment_len + 1);
+ memcpy(new_ctx->fragment, fragment, new_ctx->fragment_len + 1);
+ }
+ else {
+ new_ctx->fragment = NULL;
+ new_ctx->fragment_len = 0;
+ }
+
+ if (!new_ctx->query) {
+ new_ctx->full_path = new_ctx->path;
+ new_ctx->full_path_len = new_ctx->path_len;
+ }
+ else {
+ new_ctx->full_path_len = new_ctx->path_len + new_ctx->query_len;
+ new_ctx->full_path =
+ (char*)serf_bucket_mem_alloc(ctx->app_ctx->bkt_alloc,
+ new_ctx->full_path_len + 1);
+ memcpy(new_ctx->full_path, new_ctx->path, new_ctx->path_len);
+ memcpy(new_ctx->full_path + new_ctx->path_len, new_ctx->query,
+ new_ctx->query_len + 1);
+ }
+
+ new_ctx->hdr_read = 0;
+
+ new_ctx->doc_queue_condvar = ctx->condvar;
+ new_ctx->doc_queue = ctx->doc_queue;
+ new_ctx->doc_queue_alloc = ctx->doc_queue_alloc;
+
+ new_ctx->acceptor = accept_response;
+ new_ctx->acceptor_baton = &ctx->app_ctx;
+ new_ctx->handler = handle_response;
+
+ apr_atomic_inc32(ctx->requests_outstanding);
+
+ serf_connection_request_create(ctx->connection, setup_request, new_ctx);
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t put_req(const char *c, const char *orig_path,
+ parser_baton_t *ctx, apr_pool_t *pool)
+{
+ apr_status_t status;
+ apr_uri_t url;
+
+ /* Build url */
+#ifdef SERF_VERBOSE
+ printf("Url discovered: %s\n", c);
+#endif
+
+ status = apr_uri_parse(pool, c, &url);
+
+ /* We got something that was minimally useful. */
+ if (status == 0 && url.path) {
+ const char *path, *query, *fragment;
+
+ /* This is likely a relative URL. So, merge and hope for the
+ * best.
+ */
+ if (!url.hostinfo && url.path[0] != '/') {
+ struct iovec vec[2];
+ char *c;
+ apr_size_t nbytes;
+
+ c = strrchr(orig_path, '/');
+
+ /* assert c */
+ if (!c) {
+ return APR_EGENERAL;
+ }
+
+ vec[0].iov_base = (char*)orig_path;
+ vec[0].iov_len = c - orig_path + 1;
+
+ /* If the HTML is cute and gives us ./foo - skip the ./ */
+ if (url.path[0] == '.' && url.path[1] == '/') {
+ vec[1].iov_base = url.path + 2;
+ vec[1].iov_len = strlen(url.path + 2);
+ }
+ else if (url.path[0] == '.' && url.path[1] == '.') {
+ /* FIXME We could be cute and consolidate the path; we're a
+ * toy example. So no.
+ */
+ vec[1].iov_base = url.path;
+ vec[1].iov_len = strlen(url.path);
+ }
+ else {
+ vec[1].iov_base = url.path;
+ vec[1].iov_len = strlen(url.path);
+ }
+
+ path = apr_pstrcatv(pool, vec, 2, &nbytes);
+ }
+ else {
+ path = url.path;
+ }
+
+ query = url.query;
+ fragment = url.fragment;
+
+ return create_request(url.hostinfo, path, query, fragment, ctx, pool);
+ }
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t find_href(apr_xml_elem *e, const char *orig_path,
+ parser_baton_t *ctx, apr_pool_t *pool)
+{
+ apr_status_t status;
+
+ do {
+ /* print */
+ if (e->name[0] == 'a' && e->name[1] == '\0') {
+ apr_xml_attr *a;
+
+ a = e->attr;
+ while (a) {
+ if (strcasecmp(a->name, "href") == 0) {
+ break;
+ }
+ a = a->next;
+ }
+ if (a) {
+ status = put_req(a->value, orig_path, ctx, pool);
+ if (status) {
+ return status;
+ }
+ }
+ }
+
+ if (e->first_child) {
+ status = find_href(e->first_child, orig_path, ctx, pool);
+ if (status) {
+ return status;
+ }
+ }
+
+ e = e->next;
+ }
+ while (e);
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t find_href_doc(apr_xml_doc *doc, const char *path,
+ parser_baton_t *ctx,
+ apr_pool_t *pool)
+{
+ return find_href(doc->root, path, ctx, pool);
+}
+
+static void * APR_THREAD_FUNC parser_thread(apr_thread_t *thread, void *data)
+{
+ apr_status_t status;
+ apr_pool_t *pool, *subpool;
+ parser_baton_t *ctx;
+
+ ctx = (parser_baton_t*)data;
+ pool = apr_thread_pool_get(thread);
+
+ apr_pool_create(&subpool, pool);
+
+ while (1) {
+ doc_path_t *dup;
+
+ apr_pool_clear(subpool);
+
+ /* Grab it. */
+ apr_thread_mutex_lock(ctx->mutex);
+ /* Sleep. */
+ apr_thread_cond_wait(ctx->condvar, ctx->mutex);
+
+ /* Fetch the doc off the list. */
+ if (ctx->doc_queue->nelts) {
+ dup = *(doc_path_t**)(apr_array_pop(ctx->doc_queue));
+ /* dup = (ctx->doc_queue->conns->elts)[0]; */
+ }
+ else {
+ dup = NULL;
+ }
+
+ /* Don't need the mutex now. */
+ apr_thread_mutex_unlock(ctx->mutex);
+
+ /* Parse the doc/url pair. */
+ if (dup) {
+ status = find_href_doc(dup->doc, dup->path, ctx, subpool);
+ if (status) {
+ printf("Error finding hrefs: %d %s\n", status, dup->path);
+ }
+ /* Free the doc pair and its pool. */
+ apr_pool_destroy(dup->pool);
+ serf_bucket_mem_free(ctx->doc_queue_alloc, dup->path);
+ serf_bucket_mem_free(ctx->doc_queue_alloc, dup);
+ }
+
+ /* Hey are we done? */
+ if (!apr_atomic_read32(ctx->requests_outstanding)) {
+ break;
+ }
+ }
+ return NULL;
+}
+
+static void print_usage(apr_pool_t *pool)
+{
+ puts("serf_get [options] URL");
+ puts("-h\tDisplay this help");
+ puts("-v\tDisplay version");
+ puts("-H\tPrint response headers");
+ puts("-a <user:password> Present Basic authentication credentials");
+}
+
+int main(int argc, const char **argv)
+{
+ apr_status_t status;
+ apr_pool_t *pool;
+ apr_sockaddr_t *address;
+ serf_context_t *context;
+ serf_connection_t *connection;
+ app_baton_t app_ctx;
+ handler_baton_t *handler_ctx;
+ apr_uri_t url;
+ const char *raw_url, *method;
+ int count;
+ apr_getopt_t *opt;
+ char opt_c;
+ char *authn = NULL;
+ const char *opt_arg;
+
+ /* For the parser threads */
+ apr_thread_t *thread[3];
+ apr_threadattr_t *tattr;
+ apr_status_t parser_status;
+ parser_baton_t *parser_ctx;
+
+ apr_initialize();
+ atexit(apr_terminate);
+
+ apr_pool_create(&pool, NULL);
+ apr_atomic_init(pool);
+ /* serf_initialize(); */
+
+ /* Default to one round of fetching. */
+ count = 1;
+ /* Default to GET. */
+ method = "GET";
+
+ apr_getopt_init(&opt, pool, argc, argv);
+
+ while ((status = apr_getopt(opt, "a:hv", &opt_c, &opt_arg)) ==
+ APR_SUCCESS) {
+ int srclen, enclen;
+
+ switch (opt_c) {
+ case 'a':
+ srclen = strlen(opt_arg);
+ enclen = apr_base64_encode_len(srclen);
+ authn = apr_palloc(pool, enclen + 6);
+ strcpy(authn, "Basic ");
+ (void) apr_base64_encode(&authn[6], opt_arg, srclen);
+ break;
+ case 'h':
+ print_usage(pool);
+ exit(0);
+ break;
+ case 'v':
+ puts("Serf version: " SERF_VERSION_STRING);
+ exit(0);
+ default:
+ break;
+ }
+ }
+
+ if (opt->ind != opt->argc - 1) {
+ print_usage(pool);
+ exit(-1);
+ }
+
+ raw_url = argv[opt->ind];
+
+ apr_uri_parse(pool, raw_url, &url);
+ if (!url.port) {
+ url.port = apr_uri_port_of_scheme(url.scheme);
+ }
+ if (!url.path) {
+ url.path = "/";
+ }
+
+ if (strcasecmp(url.scheme, "https") == 0) {
+ app_ctx.using_ssl = 1;
+ }
+ else {
+ app_ctx.using_ssl = 0;
+ }
+
+ status = apr_sockaddr_info_get(&address,
+ url.hostname, APR_UNSPEC, url.port, 0,
+ pool);
+ if (status) {
+ printf("Error creating address: %d\n", status);
+ exit(1);
+ }
+
+ context = serf_context_create(pool);
+
+ /* ### Connection or Context should have an allocator? */
+ app_ctx.bkt_alloc = serf_bucket_allocator_create(pool, NULL, NULL);
+ app_ctx.ssl_ctx = NULL;
+ app_ctx.authn = authn;
+
+ connection = serf_connection_create(context, address,
+ conn_setup, &app_ctx,
+ closed_connection, &app_ctx,
+ pool);
+
+ handler_ctx = (handler_baton_t*)serf_bucket_mem_alloc(app_ctx.bkt_alloc,
+ sizeof(handler_baton_t));
+ handler_ctx->allocator = app_ctx.bkt_alloc;
+ handler_ctx->doc_queue = apr_array_make(pool, 1, sizeof(doc_path_t*));
+ handler_ctx->doc_queue_alloc = app_ctx.bkt_alloc;
+
+ handler_ctx->requests_outstanding =
+ (apr_uint32_t*)serf_bucket_mem_alloc(app_ctx.bkt_alloc,
+ sizeof(apr_uint32_t));
+ apr_atomic_set32(handler_ctx->requests_outstanding, 0);
+ handler_ctx->hdr_read = 0;
+
+ parser_ctx = (void*)serf_bucket_mem_alloc(app_ctx.bkt_alloc,
+ sizeof(parser_baton_t));
+
+ parser_ctx->requests_outstanding = handler_ctx->requests_outstanding;
+ parser_ctx->connection = connection;
+ parser_ctx->app_ctx = &app_ctx;
+ parser_ctx->doc_queue = handler_ctx->doc_queue;
+ parser_ctx->doc_queue_alloc = handler_ctx->doc_queue_alloc;
+ /* Restrict ourselves to this host. */
+ parser_ctx->hostinfo = url.hostinfo;
+
+ status = apr_thread_mutex_create(&parser_ctx->mutex,
+ APR_THREAD_MUTEX_DEFAULT, pool);
+ if (status) {
+ printf("Couldn't create mutex %d\n", status);
+ return status;
+ }
+
+ status = apr_thread_cond_create(&parser_ctx->condvar, pool);
+ if (status) {
+ printf("Couldn't create condvar: %d\n", status);
+ return status;
+ }
+
+ /* Let the handler now which condvar to use. */
+ handler_ctx->doc_queue_condvar = parser_ctx->condvar;
+
+ apr_threadattr_create(&tattr, pool);
+
+ /* Start the parser thread. */
+ apr_thread_create(&thread[0], tattr, parser_thread, parser_ctx, pool);
+
+ /* Deliver the first request. */
+ create_request(url.hostinfo, url.path, NULL, NULL, parser_ctx, pool);
+
+ /* Go run our normal thread. */
+ while (1) {
+ int tries = 0;
+
+ status = serf_context_run(context, SERF_DURATION_FOREVER, pool);
+ if (APR_STATUS_IS_TIMEUP(status))
+ continue;
+ if (status) {
+ char buf[200];
+
+ printf("Error running context: (%d) %s\n", status,
+ apr_strerror(status, buf, sizeof(buf)));
+ exit(1);
+ }
+
+ /* We run this check to allow our parser threads to add more
+ * requests to our queue.
+ */
+ for (tries = 0; tries < 3; tries++) {
+ if (!apr_atomic_read32(handler_ctx->requests_outstanding)) {
+#ifdef SERF_VERBOSE
+ printf("Waiting...");
+#endif
+ apr_sleep(100000);
+#ifdef SERF_VERBOSE
+ printf("Done\n");
+#endif
+ }
+ else {
+ break;
+ }
+ }
+ if (tries >= 3) {
+ break;
+ }
+ /* Debugging purposes only! */
+ serf_debug__closed_conn(app_ctx.bkt_alloc);
+ }
+
+ printf("Quitting...\n");
+ serf_connection_close(connection);
+
+ /* wake up the parser via condvar signal */
+ apr_thread_cond_signal(parser_ctx->condvar);
+
+ status = apr_thread_join(&parser_status, thread[0]);
+ if (status) {
+ printf("Error joining thread: %d\n", status);
+ return status;
+ }
+
+ serf_bucket_mem_free(app_ctx.bkt_alloc, handler_ctx->requests_outstanding);
+ serf_bucket_mem_free(app_ctx.bkt_alloc, parser_ctx);
+
+ apr_pool_destroy(pool);
+ return 0;
+}
diff --git a/test/serftestca.pem b/test/serftestca.pem
new file mode 100644
index 0000000..15aa004
--- /dev/null
+++ b/test/serftestca.pem
@@ -0,0 +1,66 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ c2:31:db:41:c9:7b:a9:46
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=BE, ST=Antwerp, O=In Serf we trust, Inc., OU=Test Suite, CN=Serf/emailAddress=serf@example.com, L=Mechelen
+ Validity
+ Not Before: Mar 21 13:18:17 2008 GMT
+ Not After : Mar 21 13:18:17 2011 GMT
+ Subject: C=BE, ST=Antwerp, O=In Serf we trust, Inc., OU=Test Suite, CN=Serf/emailAddress=serf@example.com, L=Mechelen
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:a1:21:58:60:ea:79:ae:9f:0f:f4:69:b5:af:55:
+ 9a:8b:da:1d:74:80:88:44:42:46:64:59:98:3e:84:
+ e1:70:f7:18:e1:7c:8d:cc:42:27:cd:e6:31:47:51:
+ 66:3d:58:1c:f9:54:26:4f:12:b7:0e:46:a7:27:c1:
+ ca:ac:a7:38:0f:a1:00:fb:a8:20:77:37:14:6a:7b:
+ 65:34:1c:eb:30:fa:0b:9e:57:2c:7a:04:13:50:d4:
+ e2:7c:66:5f:97:45:75:78:47:f5:9e:68:9d:40:b9:
+ 94:d3:78:50:c8:19:10:50:52:fd:2f:b8:a1:75:74:
+ ad:73:95:46:9a:8e:95:b2:3d
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ 15:BB:2D:8C:63:10:0D:31:01:D1:C6:26:01:27:43:A5:F4:57:6E:ED
+ X509v3 Authority Key Identifier:
+ keyid:15:BB:2D:8C:63:10:0D:31:01:D1:C6:26:01:27:43:A5:F4:57:6E:ED
+ DirName:/C=BE/ST=Antwerp/O=In Serf we trust, Inc./OU=Test Suite/CN=Serf/emailAddress=serf@example.com/L=Mechelen
+ serial:C2:31:DB:41:C9:7B:A9:46
+
+ X509v3 Basic Constraints:
+ CA:TRUE
+ Signature Algorithm: sha1WithRSAEncryption
+ 59:9c:b0:62:cc:a4:c0:98:68:4b:52:bf:fa:84:ee:b5:65:d5:
+ a7:51:39:77:a0:be:d6:14:b0:7a:64:2f:0d:ee:49:e8:b6:6a:
+ c7:1d:5f:bc:27:4c:25:4b:25:b7:69:5c:07:86:54:69:22:99:
+ d9:1a:5d:dd:38:c9:00:b4:29:89:7d:ce:df:b5:3f:57:05:ee:
+ 5b:0e:a4:f0:bc:7a:4f:1b:ba:84:85:e8:0f:e3:6b:fa:6f:cf:
+ 3f:23:7c:d0:dd:c8:95:91:46:8a:05:84:84:46:cf:e3:c8:fc:
+ 9c:94:c2:dd:15:d4:6e:d1:31:0b:d9:7b:ce:1e:13:72:c9:2e:
+ a9:86
+-----BEGIN CERTIFICATE-----
+MIIDsjCCAxugAwIBAgIJAMIx20HJe6lGMA0GCSqGSIb3DQEBBQUAMIGYMQswCQYD
+VQQGEwJCRTEQMA4GA1UECBMHQW50d2VycDEfMB0GA1UEChMWSW4gU2VyZiB3ZSB0
+cnVzdCwgSW5jLjETMBEGA1UECxMKVGVzdCBTdWl0ZTENMAsGA1UEAxMEU2VyZjEf
+MB0GCSqGSIb3DQEJARYQc2VyZkBleGFtcGxlLmNvbTERMA8GA1UEBxMITWVjaGVs
+ZW4wHhcNMDgwMzIxMTMxODE3WhcNMTEwMzIxMTMxODE3WjCBmDELMAkGA1UEBhMC
+QkUxEDAOBgNVBAgTB0FudHdlcnAxHzAdBgNVBAoTFkluIFNlcmYgd2UgdHJ1c3Qs
+IEluYy4xEzARBgNVBAsTClRlc3QgU3VpdGUxDTALBgNVBAMTBFNlcmYxHzAdBgkq
+hkiG9w0BCQEWEHNlcmZAZXhhbXBsZS5jb20xETAPBgNVBAcTCE1lY2hlbGVuMIGf
+MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQChIVhg6nmunw/0abWvVZqL2h10gIhE
+QkZkWZg+hOFw9xjhfI3MQifN5jFHUWY9WBz5VCZPErcORqcnwcqspzgPoQD7qCB3
+NxRqe2U0HOsw+gueVyx6BBNQ1OJ8Zl+XRXV4R/WeaJ1AuZTTeFDIGRBQUv0vuKF1
+dK1zlUaajpWyPQIDAQABo4IBADCB/TAdBgNVHQ4EFgQUFbstjGMQDTEB0cYmASdD
+pfRXbu0wgc0GA1UdIwSBxTCBwoAUFbstjGMQDTEB0cYmASdDpfRXbu2hgZ6kgZsw
+gZgxCzAJBgNVBAYTAkJFMRAwDgYDVQQIEwdBbnR3ZXJwMR8wHQYDVQQKExZJbiBT
+ZXJmIHdlIHRydXN0LCBJbmMuMRMwEQYDVQQLEwpUZXN0IFN1aXRlMQ0wCwYDVQQD
+EwRTZXJmMR8wHQYJKoZIhvcNAQkBFhBzZXJmQGV4YW1wbGUuY29tMREwDwYDVQQH
+EwhNZWNoZWxlboIJAMIx20HJe6lGMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF
+BQADgYEAWZywYsykwJhoS1K/+oTutWXVp1E5d6C+1hSwemQvDe5J6LZqxx1fvCdM
+JUslt2lcB4ZUaSKZ2Rpd3TjJALQpiX3O37U/VwXuWw6k8Lx6Txu6hIXoD+Nr+m/P
+PyN80N3IlZFGigWEhEbP48j8nJTC3RXUbtExC9l7zh4TcskuqYY=
+-----END CERTIFICATE-----
diff --git a/test/server/test_server.c b/test/server/test_server.c
new file mode 100644
index 0000000..1ad2c9e
--- /dev/null
+++ b/test/server/test_server.c
@@ -0,0 +1,359 @@
+/* Copyright 2011 Justin Erenkrantz and Greg Stein
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr.h"
+#include "apr_pools.h"
+#include <apr_poll.h>
+#include <apr_version.h>
+#include <stdlib.h>
+
+#include "serf.h"
+
+#include "test_server.h"
+
+struct serv_ctx_t {
+ /* Pool for resource allocation. */
+ apr_pool_t *pool;
+
+ apr_int32_t options;
+
+ /* Array of actions which server will replay when client connected. */
+ test_server_action_t *action_list;
+ /* Size of action_list array. */
+ apr_size_t action_count;
+ /* Index of current action. */
+ apr_size_t cur_action;
+
+ /* Array of messages the server will receive from the client. */
+ test_server_message_t *message_list;
+ /* Size of message_list array. */
+ apr_size_t message_count;
+ /* Index of current message. */
+ apr_size_t cur_message;
+
+ /* Number of messages received that the server didn't respond to yet. */
+ apr_size_t outstanding_responses;
+
+ /* Position in message buffer (incoming messages being read). */
+ apr_size_t message_buf_pos;
+
+ /* Position in action buffer. (outgoing messages being sent). */
+ apr_size_t action_buf_pos;
+
+ /* Address for server binding. */
+ apr_sockaddr_t *serv_addr;
+ apr_socket_t *serv_sock;
+
+ /* Accepted client socket. NULL if there is no client socket. */
+ apr_socket_t *client_sock;
+
+};
+
+/* Replay support functions */
+static void next_message(serv_ctx_t *servctx)
+{
+ servctx->cur_message++;
+}
+
+static void next_action(serv_ctx_t *servctx)
+{
+ servctx->cur_action++;
+ servctx->action_buf_pos = 0;
+}
+
+/* Verify received requests and take the necessary actions
+ (return a response, kill the connection ...) */
+static apr_status_t replay(serv_ctx_t *servctx,
+ apr_int16_t rtnevents,
+ apr_pool_t *pool)
+{
+ apr_status_t status = APR_SUCCESS;
+ test_server_action_t *action;
+
+ if (rtnevents & APR_POLLIN) {
+ if (servctx->message_list == NULL) {
+ /* we're not expecting any requests to reach this server! */
+ printf("Received request where none was expected\n");
+
+ return APR_EGENERAL;
+ }
+
+ if (servctx->cur_action >= servctx->action_count) {
+ char buf[128];
+ apr_size_t len = sizeof(buf);
+
+ status = apr_socket_recv(servctx->client_sock, buf, &len);
+ if (! APR_STATUS_IS_EAGAIN(status)) {
+ /* we're out of actions! */
+ printf("Received more requests than expected.\n");
+
+ return APR_EGENERAL;
+ }
+ return status;
+ }
+
+ action = &servctx->action_list[servctx->cur_action];
+
+ if (action->kind == SERVER_IGNORE_AND_KILL_CONNECTION) {
+ char buf[128];
+ apr_size_t len = sizeof(buf);
+
+ status = apr_socket_recv(servctx->client_sock, buf, &len);
+
+ if (status == APR_EOF) {
+ apr_socket_close(servctx->client_sock);
+ servctx->client_sock = NULL;
+ next_action(servctx);
+ return APR_SUCCESS;
+ }
+
+ return status;
+ }
+ else if (action->kind == SERVER_RECV ||
+ (action->kind == SERVER_RESPOND &&
+ servctx->outstanding_responses == 0)) {
+ apr_size_t msg_len, len;
+ char buf[128];
+ test_server_message_t *message;
+
+ message = &servctx->message_list[servctx->cur_message];
+ msg_len = strlen(message->text);
+
+ len = msg_len - servctx->message_buf_pos;
+ if (len > sizeof(buf))
+ len = sizeof(buf);
+
+ status = apr_socket_recv(servctx->client_sock, buf, &len);
+ if (status != APR_SUCCESS)
+ return status;
+
+ if (servctx->options & TEST_SERVER_DUMP)
+ fwrite(buf, len, 1, stdout);
+
+ if (strncmp(buf, message->text + servctx->message_buf_pos, len) != 0) {
+ /* ## TODO: Better diagnostics. */
+ printf("Expected: (\n");
+ fwrite(message->text + servctx->message_buf_pos, len, 1, stdout);
+ printf(")\n");
+ printf("Actual: (\n");
+ fwrite(buf, len, 1, stdout);
+ printf(")\n");
+
+ return APR_EGENERAL;
+ }
+
+ servctx->message_buf_pos += len;
+
+ if (servctx->message_buf_pos >= msg_len) {
+ next_message(servctx);
+ servctx->message_buf_pos -= msg_len;
+ if (action->kind == SERVER_RESPOND)
+ servctx->outstanding_responses++;
+ if (action->kind == SERVER_RECV)
+ next_action(servctx);
+ }
+ }
+ }
+ if (rtnevents & APR_POLLOUT) {
+ action = &servctx->action_list[servctx->cur_action];
+
+ if (action->kind == SERVER_RESPOND && servctx->outstanding_responses) {
+ apr_size_t msg_len;
+ apr_size_t len;
+
+ msg_len = strlen(action->text);
+ len = msg_len - servctx->action_buf_pos;
+
+ status = apr_socket_send(servctx->client_sock,
+ action->text + servctx->action_buf_pos, &len);
+ if (status != APR_SUCCESS)
+ return status;
+
+ if (servctx->options & TEST_SERVER_DUMP)
+ fwrite(action->text + servctx->action_buf_pos, len, 1, stdout);
+
+ servctx->action_buf_pos += len;
+
+ if (servctx->action_buf_pos >= msg_len) {
+ next_action(servctx);
+ servctx->outstanding_responses--;
+ }
+ }
+ else if (action->kind == SERVER_KILL_CONNECTION ||
+ action->kind == SERVER_IGNORE_AND_KILL_CONNECTION) {
+ apr_socket_close(servctx->client_sock);
+ servctx->client_sock = NULL;
+ next_action(servctx);
+ }
+ }
+ else if (rtnevents & APR_POLLIN) {
+ /* ignore */
+ }
+ else {
+ printf("Unknown rtnevents: %d\n", rtnevents);
+ abort();
+ }
+
+ return status;
+}
+
+apr_status_t test_server_run(serv_ctx_t *servctx,
+ apr_short_interval_time_t duration,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+ apr_pollset_t *pollset;
+ apr_int32_t num;
+ const apr_pollfd_t *desc;
+
+ /* create a new pollset */
+ status = apr_pollset_create(&pollset, 32, pool, 0);
+ if (status != APR_SUCCESS)
+ return status;
+
+ /* Don't accept new connection while processing client connection. At
+ least for present time.*/
+ if (servctx->client_sock) {
+ apr_pollfd_t pfd = { pool, APR_POLL_SOCKET, APR_POLLIN | APR_POLLOUT, 0,
+ { NULL }, NULL };
+ pfd.desc.s = servctx->client_sock;
+ status = apr_pollset_add(pollset, &pfd);
+ if (status != APR_SUCCESS)
+ goto cleanup;
+ }
+ else {
+ apr_pollfd_t pfd = { pool, APR_POLL_SOCKET, APR_POLLIN, 0,
+ { NULL }, NULL };
+ pfd.desc.s = servctx->serv_sock;
+ status = apr_pollset_add(pollset, &pfd);
+ if (status != APR_SUCCESS)
+ goto cleanup;
+ }
+
+ status = apr_pollset_poll(pollset, APR_USEC_PER_SEC >> 1, &num, &desc);
+ if (status != APR_SUCCESS)
+ goto cleanup;
+
+ while (num--) {
+ if (desc->desc.s == servctx->serv_sock) {
+ status = apr_socket_accept(&servctx->client_sock, servctx->serv_sock,
+ servctx->pool);
+ if (status != APR_SUCCESS)
+ goto cleanup;
+
+ apr_socket_opt_set(servctx->client_sock, APR_SO_NONBLOCK, 1);
+ apr_socket_timeout_set(servctx->client_sock, 0);
+
+ status = APR_SUCCESS;
+ goto cleanup;
+ }
+
+ if (desc->desc.s == servctx->client_sock) {
+ /* Replay data to socket. */
+ status = replay(servctx, desc->rtnevents, pool);
+
+ if (APR_STATUS_IS_EOF(status)) {
+ apr_socket_close(servctx->client_sock);
+ servctx->client_sock = NULL;
+ }
+ else if (APR_STATUS_IS_EAGAIN(status)) {
+ status = APR_SUCCESS;
+ }
+ else if (status != APR_SUCCESS) {
+ /* Real error. */
+ goto cleanup;
+ }
+ }
+
+ desc++;
+ }
+
+cleanup:
+ apr_pollset_destroy(pollset);
+
+ return status;
+}
+
+/* Start a TCP server on port SERV_PORT in thread THREAD. srv_replay is a array
+ of action to replay when connection started. replay_count is count of
+ actions in srv_replay. */
+apr_status_t test_start_server(serv_ctx_t **servctx_p,
+ apr_sockaddr_t *address,
+ test_server_message_t *message_list,
+ apr_size_t message_count,
+ test_server_action_t *action_list,
+ apr_size_t action_count,
+ apr_int32_t options,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+ apr_socket_t *serv_sock;
+ serv_ctx_t *servctx;
+
+ servctx = apr_pcalloc(pool, sizeof(*servctx));
+ *servctx_p = servctx;
+
+ servctx->serv_addr = address;
+ servctx->options = options;
+ servctx->pool = pool;
+ servctx->message_list = message_list;
+ servctx->message_count = message_count;
+ servctx->action_list = action_list;
+ servctx->action_count = action_count;
+
+ /* create server socket */
+#if APR_VERSION_AT_LEAST(1, 0, 0)
+ status = apr_socket_create(&serv_sock, address->family, SOCK_STREAM, 0,
+ pool);
+#else
+ status = apr_socket_create(&serv_sock, address->family, SOCK_STREAM, pool);
+#endif
+
+ if (status != APR_SUCCESS)
+ return status;
+
+ apr_socket_opt_set(serv_sock, APR_SO_NONBLOCK, 1);
+ apr_socket_timeout_set(serv_sock, 0);
+ apr_socket_opt_set(serv_sock, APR_SO_REUSEADDR, 1);
+
+ status = apr_socket_bind(serv_sock, servctx->serv_addr);
+ if (status != APR_SUCCESS)
+ return status;
+
+ /* Start replay from first action. */
+ servctx->cur_action = 0;
+ servctx->action_buf_pos = 0;
+ servctx->outstanding_responses = 0;
+
+ /* listen for clients */
+ apr_socket_listen(serv_sock, SOMAXCONN);
+ if (status != APR_SUCCESS)
+ return status;
+
+ servctx->serv_sock = serv_sock;
+ servctx->client_sock = NULL;
+ return APR_SUCCESS;
+}
+
+apr_status_t test_server_destroy(serv_ctx_t *servctx, apr_pool_t *pool)
+{
+ apr_socket_close(servctx->serv_sock);
+
+ if (servctx->client_sock) {
+ apr_socket_close(servctx->client_sock);
+ }
+
+ return APR_SUCCESS;
+}
diff --git a/test/server/test_server.h b/test/server/test_server.h
new file mode 100644
index 0000000..9e8b0e2
--- /dev/null
+++ b/test/server/test_server.h
@@ -0,0 +1,70 @@
+/* Copyright 2011 Justin Erenkrantz and Greg Stein
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef TEST_SERVER_H
+#define TEST_SERVER_H
+
+typedef struct serv_ctx_t serv_ctx_t;
+
+#define TEST_SERVER_DUMP 1
+
+/* Default port for our test server. */
+#define SERV_PORT 12345
+#define SERV_PORT_STR "12345"
+
+#define PROXY_PORT 23456
+
+typedef struct
+{
+ enum {
+ SERVER_RECV,
+ SERVER_SEND,
+ SERVER_RESPOND,
+ SERVER_IGNORE_AND_KILL_CONNECTION,
+ SERVER_KILL_CONNECTION
+ } kind;
+
+ const char *text;
+} test_server_action_t;
+
+typedef struct
+{
+ const char *text;
+} test_server_message_t;
+
+apr_status_t test_start_server(serv_ctx_t **servctx_p,
+ apr_sockaddr_t *address,
+ test_server_message_t *message_list,
+ apr_size_t message_count,
+ test_server_action_t *action_list,
+ apr_size_t action_count,
+ apr_int32_t options,
+ apr_pool_t *pool);
+
+apr_status_t test_server_run(serv_ctx_t *servctx,
+ apr_short_interval_time_t duration,
+ apr_pool_t *pool);
+
+apr_status_t test_server_destroy(serv_ctx_t *servctx, apr_pool_t *pool);
+
+#ifndef APR_VERSION_AT_LEAST /* Introduced in APR 1.3.0 */
+#define APR_VERSION_AT_LEAST(major,minor,patch) \
+(((major) < APR_MAJOR_VERSION) \
+ || ((major) == APR_MAJOR_VERSION && (minor) < APR_MINOR_VERSION) \
+ || ((major) == APR_MAJOR_VERSION && (minor) == APR_MINOR_VERSION && \
+ (patch) <= APR_PATCH_VERSION))
+#endif /* APR_VERSION_AT_LEAST */
+
+#endif /* TEST_SERVER_H */
diff --git a/test/test_all.c b/test/test_all.c
new file mode 100644
index 0000000..4cc4c8c
--- /dev/null
+++ b/test/test_all.c
@@ -0,0 +1,101 @@
+/* Copyright 2002-2007 Justin Erenkrantz and Greg Stein
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr.h"
+#include "apr_pools.h"
+#include "test_serf.h"
+#include <stdlib.h>
+
+static const struct testlist {
+ const char *testname;
+ CuSuite *(*func)(void);
+} tests[] = {
+ {"context", test_context},
+ {"buckets", test_buckets},
+ {"ssl", test_ssl},
+ {"LastTest", NULL}
+};
+
+int main(int argc, char *argv[])
+{
+ CuSuite *alltests = NULL;
+ CuString *output = CuStringNew();
+ int i;
+ int list_provided = 0;
+ int exit_code;
+
+ apr_initialize();
+ atexit(apr_terminate);
+
+ for (i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "-v")) {
+ continue;
+ }
+ if (!strcmp(argv[i], "-l")) {
+ for (i = 0; tests[i].func != NULL; i++) {
+ printf("%s\n", tests[i].testname);
+ }
+ exit(0);
+ }
+ if (argv[i][0] == '-') {
+ fprintf(stderr, "invalid option: `%s'\n", argv[i]);
+ exit(1);
+ }
+ list_provided = 1;
+ }
+
+ alltests = CuSuiteNew();
+ if (!list_provided) {
+ /* add everything */
+ for (i = 0; tests[i].func != NULL; i++) {
+ CuSuite *st = tests[i].func();
+ CuSuiteAddSuite(alltests, st);
+ CuSuiteFree(st);
+ }
+ }
+ else {
+ /* add only the tests listed */
+ for (i = 1; i < argc; i++) {
+ int j;
+ int found = 0;
+
+ if (argv[i][0] == '-') {
+ continue;
+ }
+ for (j = 0; tests[j].func != NULL; j++) {
+ if (!strcmp(argv[i], tests[j].testname)) {
+ CuSuiteAddSuite(alltests, tests[j].func());
+ found = 1;
+ }
+ }
+ if (!found) {
+ fprintf(stderr, "invalid test name: `%s'\n", argv[i]);
+ exit(1);
+ }
+ }
+ }
+
+ CuSuiteRun(alltests);
+ CuSuiteSummary(alltests, output);
+ CuSuiteDetails(alltests, output);
+ printf("%s\n", output->buffer);
+
+ exit_code = alltests->failCount > 0 ? 1 : 0;
+
+ CuSuiteFreeDeep(alltests);
+ CuStringFree(output);
+
+ return exit_code;
+}
diff --git a/test/test_buckets.c b/test/test_buckets.c
new file mode 100644
index 0000000..7d4f7ac
--- /dev/null
+++ b/test/test_buckets.c
@@ -0,0 +1,425 @@
+/* Copyright 2002-2007 Justin Erenkrantz and Greg Stein
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_strings.h>
+
+#include "serf.h"
+#include "test_serf.h"
+
+#define CRLF "\r\n"
+
+static void test_simple_bucket_readline(CuTest *tc)
+{
+ apr_status_t status;
+ serf_bucket_t *bkt;
+ const char *data;
+ int found;
+ apr_size_t len;
+
+ apr_pool_t *test_pool = test_setup();
+ serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(test_pool, NULL,
+ NULL);
+
+ bkt = SERF_BUCKET_SIMPLE_STRING(
+ "line1" CRLF
+ "line2",
+ alloc);
+
+ /* Initialize parameters to check that they will be initialized. */
+ len = 0x112233;
+ data = 0;
+ status = serf_bucket_readline(bkt, SERF_NEWLINE_CRLF, &found, &data, &len);
+
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+ CuAssertIntEquals(tc, SERF_NEWLINE_CRLF, found);
+ CuAssertIntEquals(tc, 7, len);
+ CuAssert(tc, data, strncmp("line1" CRLF, data, len) == 0);
+
+ /* Initialize parameters to check that they will be initialized. */
+ len = 0x112233;
+ data = 0;
+ status = serf_bucket_readline(bkt, SERF_NEWLINE_CRLF, &found, &data, &len);
+
+ CuAssertIntEquals(tc, APR_EOF, status);
+ CuAssertIntEquals(tc, SERF_NEWLINE_NONE, found);
+ CuAssertIntEquals(tc, 5, len);
+ CuAssert(tc, data, strncmp("line2", data, len) == 0);
+ test_teardown(test_pool);
+}
+
+/* Reads bucket until EOF found and compares read data with zero terminated
+ string expected. Report all failures using CuTest. */
+static void read_and_check_bucket(CuTest *tc, serf_bucket_t *bkt,
+ const char *expected)
+{
+ apr_status_t status;
+ do
+ {
+ const char *data;
+ apr_size_t len;
+
+ status = serf_bucket_read(bkt, SERF_READ_ALL_AVAIL, &data, &len);
+ CuAssert(tc, "Got error during bucket reading.",
+ !SERF_BUCKET_READ_ERROR(status));
+ CuAssert(tc, "Read more data than expected.",
+ strlen(expected) >= len);
+ CuAssert(tc, "Read data is not equal to expected.",
+ strncmp(expected, data, len) == 0);
+
+ expected += len;
+ } while(!APR_STATUS_IS_EOF(status));
+
+ CuAssert(tc, "Read less data than expected.", strlen(expected) == 0);
+}
+
+static void test_response_bucket_read(CuTest *tc)
+{
+ serf_bucket_t *bkt, *tmp;
+
+ apr_pool_t *test_pool = test_setup();
+ serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(test_pool, NULL,
+ NULL);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING(
+ "HTTP/1.1 200 OK" CRLF
+ "Content-Length: 7" CRLF
+ CRLF
+ "abc1234",
+ alloc);
+
+ bkt = serf_bucket_response_create(tmp, alloc);
+
+ /* Read all bucket and check it content. */
+ read_and_check_bucket(tc, bkt, "abc1234");
+ test_teardown(test_pool);
+}
+
+static void test_response_bucket_headers(CuTest *tc)
+{
+ serf_bucket_t *bkt, *tmp, *hdr;
+
+ apr_pool_t *test_pool = test_setup();
+ serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(test_pool, NULL,
+ NULL);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING(
+ "HTTP/1.1 405 Method Not Allowed" CRLF
+ "Date: Sat, 12 Jun 2010 14:17:10 GMT" CRLF
+ "Server: Apache" CRLF
+ "Allow: " CRLF
+ "Content-Length: 7" CRLF
+ "Content-Type: text/html; charset=iso-8859-1" CRLF
+ "NoSpace:" CRLF
+ CRLF
+ "abc1234",
+ alloc);
+
+ bkt = serf_bucket_response_create(tmp, alloc);
+
+ /* Read all bucket and check it content. */
+ read_and_check_bucket(tc, bkt, "abc1234");
+
+ hdr = serf_bucket_response_get_headers(bkt);
+ CuAssertStrEquals(tc,
+ "",
+ serf_bucket_headers_get(hdr, "Allow"));
+ CuAssertStrEquals(tc,
+ "7",
+ serf_bucket_headers_get(hdr, "Content-Length"));
+ CuAssertStrEquals(tc,
+ "",
+ serf_bucket_headers_get(hdr, "NoSpace"));
+ test_teardown(test_pool);
+}
+
+static void test_response_bucket_chunked_read(CuTest *tc)
+{
+ serf_bucket_t *bkt, *tmp, *hdrs;
+
+ apr_pool_t *test_pool = test_setup();
+ serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(test_pool, NULL,
+ NULL);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING(
+ "HTTP/1.1 200 OK" CRLF
+ "Transfer-Encoding: chunked" CRLF
+ CRLF
+ "3" CRLF
+ "abc" CRLF
+ "4" CRLF
+ "1234" CRLF
+ "0" CRLF
+ "Footer: value" CRLF
+ CRLF,
+ alloc);
+
+ bkt = serf_bucket_response_create(tmp, alloc);
+
+ /* Read all bucket and check it content. */
+ read_and_check_bucket(tc, bkt, "abc1234");
+
+ hdrs = serf_bucket_response_get_headers(bkt);
+ CuAssertTrue(tc, hdrs != NULL);
+
+ /* Check that trailing headers parsed correctly. */
+ CuAssertStrEquals(tc, "value", serf_bucket_headers_get(hdrs, "Footer"));
+ test_teardown(test_pool);
+}
+
+static void test_bucket_header_set(CuTest *tc)
+{
+ apr_pool_t *test_pool = test_setup();
+ serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(test_pool, NULL,
+ NULL);
+ serf_bucket_t *hdrs = serf_bucket_headers_create(alloc);
+
+ CuAssertTrue(tc, hdrs != NULL);
+
+ serf_bucket_headers_set(hdrs, "Foo", "bar");
+
+ CuAssertStrEquals(tc, "bar", serf_bucket_headers_get(hdrs, "Foo"));
+
+ serf_bucket_headers_set(hdrs, "Foo", "baz");
+
+ CuAssertStrEquals(tc, "bar,baz", serf_bucket_headers_get(hdrs, "Foo"));
+
+ serf_bucket_headers_set(hdrs, "Foo", "test");
+
+ CuAssertStrEquals(tc, "bar,baz,test", serf_bucket_headers_get(hdrs, "Foo"));
+
+ // headers are case insensitive.
+ CuAssertStrEquals(tc, "bar,baz,test", serf_bucket_headers_get(hdrs, "fOo"));
+ test_teardown(test_pool);
+}
+
+static apr_status_t read_requested_bytes(serf_bucket_t *bkt,
+ apr_size_t requested,
+ const char **buf,
+ apr_size_t *len,
+ apr_pool_t *pool)
+{
+ apr_size_t current = 0;
+ const char *tmp;
+ const char *data;
+ apr_status_t status = APR_SUCCESS;
+
+ tmp = apr_pcalloc(pool, requested);
+ while (current < requested) {
+ status = serf_bucket_read(bkt, requested, &data, len);
+ memcpy((void*)(tmp + current), (void*)data, *len);
+ current += *len;
+ if (APR_STATUS_IS_EOF(status))
+ break;
+ }
+
+ *buf = tmp;
+ *len = current;
+ return status;
+}
+
+
+static void test_iovec_buckets(CuTest *tc)
+{
+ apr_status_t status;
+ serf_bucket_t *bkt, *iobkt;
+ const char *data;
+ apr_size_t len;
+ struct iovec vecs[32];
+ struct iovec tgt_vecs[32];
+ int i;
+ int vecs_used;
+
+ apr_pool_t *test_pool = test_setup();
+ serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(test_pool, NULL,
+ NULL);
+
+ /* Test 1: Read a single string in an iovec, store it in a iovec_bucket
+ and then read it back. */
+ bkt = SERF_BUCKET_SIMPLE_STRING(
+ "line1" CRLF
+ "line2",
+ alloc);
+
+ status = serf_bucket_read_iovec(bkt, SERF_READ_ALL_AVAIL, 32, vecs,
+ &vecs_used);
+
+ iobkt = serf_bucket_iovec_create(vecs, vecs_used, alloc);
+
+ /* Check available data */
+ status = serf_bucket_peek(iobkt, &data, &len);
+ CuAssertIntEquals(tc, APR_EOF, status);
+ CuAssertIntEquals(tc, strlen("line1" CRLF "line2"), len);
+
+ /* Try to read only a few bytes (less than what's in the first buffer). */
+ status = serf_bucket_read_iovec(iobkt, 3, 32, tgt_vecs, &vecs_used);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+ CuAssertIntEquals(tc, 1, vecs_used);
+ CuAssertIntEquals(tc, 3, tgt_vecs[0].iov_len);
+ CuAssert(tc, tgt_vecs[0].iov_base,
+ strncmp("lin", tgt_vecs[0].iov_base, tgt_vecs[0].iov_len) == 0);
+
+ /* Read the rest of the data. */
+ status = serf_bucket_read_iovec(iobkt, SERF_READ_ALL_AVAIL, 32, tgt_vecs,
+ &vecs_used);
+ CuAssertIntEquals(tc, APR_EOF, status);
+ CuAssertIntEquals(tc, 1, vecs_used);
+ CuAssertIntEquals(tc, strlen("e1" CRLF "line2"), tgt_vecs[0].iov_len);
+ CuAssert(tc, tgt_vecs[0].iov_base,
+ strncmp("e1" CRLF "line2", tgt_vecs[0].iov_base, tgt_vecs[0].iov_len - 3) == 0);
+
+ /* Bucket should now be empty */
+ status = serf_bucket_peek(iobkt, &data, &len);
+ CuAssertIntEquals(tc, APR_EOF, status);
+ CuAssertIntEquals(tc, 0, len);
+
+ /* Test 2: Read multiple character bufs in an iovec, then read them back
+ in bursts. */
+ for (i = 0; i < 32 ; i++) {
+ vecs[i].iov_base = apr_psprintf(test_pool, "data %02d 901234567890", i);
+ vecs[i].iov_len = strlen(vecs[i].iov_base);
+ }
+
+ iobkt = serf_bucket_iovec_create(vecs, 32, alloc);
+
+ /* Check that some data is in the buffer. Don't verify the actual data, the
+ amount of data returned is not guaranteed to be the full buffer. */
+ status = serf_bucket_peek(iobkt, &data, &len);
+ CuAssertTrue(tc, len > 0);
+ CuAssertIntEquals(tc, APR_SUCCESS, status); /* this assumes not all data is
+ returned at once,
+ not guaranteed! */
+
+ /* Read 1 buf. 20 = sizeof("data %2d 901234567890") */
+ status = serf_bucket_read_iovec(iobkt, 1 * 20, 32,
+ tgt_vecs, &vecs_used);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+ CuAssertIntEquals(tc, 1, vecs_used);
+ CuAssert(tc, tgt_vecs[0].iov_base,
+ strncmp("data 00 901234567890", tgt_vecs[0].iov_base, tgt_vecs[0].iov_len) == 0);
+
+ /* Read 2 bufs. */
+ status = serf_bucket_read_iovec(iobkt, 2 * 20, 32,
+ tgt_vecs, &vecs_used);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+ CuAssertIntEquals(tc, 2, vecs_used);
+
+ /* Read the remaining 29 bufs. */
+ vecs_used = 400; /* test if iovec code correctly resets vecs_used */
+ status = serf_bucket_read_iovec(iobkt, SERF_READ_ALL_AVAIL, 32,
+ tgt_vecs, &vecs_used);
+ CuAssertIntEquals(tc, APR_EOF, status);
+ CuAssertIntEquals(tc, 29, vecs_used);
+
+ /* Test 3: use serf_bucket_read */
+ for (i = 0; i < 32 ; i++) {
+ vecs[i].iov_base = apr_psprintf(test_pool, "DATA %02d 901234567890", i);
+ vecs[i].iov_len = strlen(vecs[i].iov_base);
+ }
+
+ iobkt = serf_bucket_iovec_create(vecs, 32, alloc);
+
+ status = serf_bucket_read(iobkt, 10, &data, &len);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+ CuAssertIntEquals(tc, 10, len);
+ CuAssert(tc, data,
+ strncmp("DATA 00 90", data, len) == 0);
+
+ status = serf_bucket_read(iobkt, 10, &data, &len);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+ CuAssertIntEquals(tc, 10, len);
+ CuAssert(tc, tgt_vecs[0].iov_base,
+ strncmp("1234567890", data, len) == 0);
+
+ for (i = 1; i < 31 ; i++) {
+ const char *exp = apr_psprintf(test_pool, "DATA %02d 901234567890", i);
+ status = serf_bucket_read(iobkt, SERF_READ_ALL_AVAIL, &data, &len);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+ CuAssertIntEquals(tc, 20, len);
+ CuAssert(tc, data,
+ strncmp(exp, data, len) == 0);
+
+ }
+
+ status = serf_bucket_read(iobkt, 20, &data, &len);
+ CuAssertIntEquals(tc, APR_EOF, status);
+ CuAssertIntEquals(tc, 20, len);
+ CuAssert(tc, data,
+ strncmp("DATA 31 901234567890", data, len) == 0);
+
+ /* Test 3: read an empty iovec */
+ iobkt = serf_bucket_iovec_create(vecs, 0, alloc);
+ status = serf_bucket_read_iovec(iobkt, SERF_READ_ALL_AVAIL, 32,
+ tgt_vecs, &vecs_used);
+ CuAssertIntEquals(tc, APR_EOF, status);
+ CuAssertIntEquals(tc, 0, vecs_used);
+
+ status = serf_bucket_read(iobkt, SERF_READ_ALL_AVAIL, &data, &len);
+ CuAssertIntEquals(tc, APR_EOF, status);
+ CuAssertIntEquals(tc, 0, len);
+
+ /* Test 4: read 0 bytes from an iovec */
+ bkt = SERF_BUCKET_SIMPLE_STRING("line1" CRLF, alloc);
+ status = serf_bucket_read_iovec(bkt, SERF_READ_ALL_AVAIL, 32, vecs,
+ &vecs_used);
+ iobkt = serf_bucket_iovec_create(vecs, vecs_used, alloc);
+ status = serf_bucket_read_iovec(iobkt, 0, 32,
+ tgt_vecs, &vecs_used);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+ CuAssertIntEquals(tc, 0, vecs_used);
+
+ test_teardown(test_pool);
+}
+
+static void test_aggregate_buckets(CuTest *tc)
+{
+ apr_status_t status;
+ serf_bucket_t *bkt, *aggbkt;
+ struct iovec tgt_vecs[32];
+ int vecs_used;
+
+ apr_pool_t *test_pool = test_setup();
+ serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(test_pool, NULL,
+ NULL);
+
+ /* Test 1: read 0 bytes from an aggregate */
+ aggbkt = serf_bucket_aggregate_create(alloc);
+
+ bkt = SERF_BUCKET_SIMPLE_STRING("line1" CRLF, alloc);
+ serf_bucket_aggregate_append(aggbkt, bkt);
+
+ status = serf_bucket_read_iovec(aggbkt, 0, 32,
+ tgt_vecs, &vecs_used);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+ CuAssertIntEquals(tc, 0, vecs_used);
+
+ test_teardown(test_pool);
+}
+
+CuSuite *test_buckets(void)
+{
+ CuSuite *suite = CuSuiteNew();
+
+ SUITE_ADD_TEST(suite, test_simple_bucket_readline);
+ SUITE_ADD_TEST(suite, test_response_bucket_read);
+ SUITE_ADD_TEST(suite, test_response_bucket_headers);
+ SUITE_ADD_TEST(suite, test_response_bucket_chunked_read);
+ SUITE_ADD_TEST(suite, test_bucket_header_set);
+ SUITE_ADD_TEST(suite, test_iovec_buckets);
+ SUITE_ADD_TEST(suite, test_aggregate_buckets);
+
+ return suite;
+}
diff --git a/test/test_context.c b/test/test_context.c
new file mode 100644
index 0000000..d2a24d0
--- /dev/null
+++ b/test/test_context.c
@@ -0,0 +1,1011 @@
+/* Copyright 2002-2007 Justin Erenkrantz and Greg Stein
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_strings.h>
+#include <apr_version.h>
+
+#include "serf.h"
+
+#include "test_serf.h"
+#include "server/test_server.h"
+
+typedef struct {
+ serf_response_acceptor_t acceptor;
+ void *acceptor_baton;
+
+ serf_response_handler_t handler;
+
+ apr_array_header_t *sent_requests;
+ apr_array_header_t *accepted_requests;
+ apr_array_header_t *handled_requests;
+ int req_id;
+
+ const char *method;
+ const char *path;
+ int done;
+
+ test_baton_t *tb;
+} handler_baton_t;
+
+static serf_bucket_t* accept_response(serf_request_t *request,
+ serf_bucket_t *stream,
+ void *acceptor_baton,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *c;
+ serf_bucket_alloc_t *bkt_alloc;
+ handler_baton_t *ctx = acceptor_baton;
+
+ /* get the per-request bucket allocator */
+ bkt_alloc = serf_request_get_alloc(request);
+
+ /* Create a barrier so the response doesn't eat us! */
+ c = serf_bucket_barrier_create(stream, bkt_alloc);
+
+ APR_ARRAY_PUSH(ctx->accepted_requests, int) = ctx->req_id;
+
+ return serf_bucket_response_create(c, bkt_alloc);
+}
+
+static apr_status_t setup_request(serf_request_t *request,
+ void *setup_baton,
+ serf_bucket_t **req_bkt,
+ serf_response_acceptor_t *acceptor,
+ void **acceptor_baton,
+ serf_response_handler_t *handler,
+ void **handler_baton,
+ apr_pool_t *pool)
+{
+ handler_baton_t *ctx = setup_baton;
+ serf_bucket_t *body_bkt;
+
+ /* create a simple body text */
+ const char *str = apr_psprintf(pool, "%d", ctx->req_id);
+ body_bkt = serf_bucket_simple_create(str, strlen(str), NULL, NULL,
+ serf_request_get_alloc(request));
+ *req_bkt =
+ serf_request_bucket_request_create(request,
+ ctx->method, ctx->path,
+ body_bkt,
+ serf_request_get_alloc(request));
+
+ APR_ARRAY_PUSH(ctx->sent_requests, int) = ctx->req_id;
+
+ *acceptor = ctx->acceptor;
+ *acceptor_baton = ctx;
+ *handler = ctx->handler;
+ *handler_baton = ctx;
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t handle_response(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ handler_baton_t *ctx = handler_baton;
+
+ if (! response) {
+ serf_connection_request_create(ctx->tb->connection,
+ setup_request,
+ ctx);
+ return APR_SUCCESS;
+ }
+
+ while (1) {
+ apr_status_t status;
+ const char *data;
+ apr_size_t len;
+
+ status = serf_bucket_read(response, 2048, &data, &len);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return status;
+
+ if (APR_STATUS_IS_EOF(status)) {
+ APR_ARRAY_PUSH(ctx->handled_requests, int) = ctx->req_id;
+ ctx->done = TRUE;
+ return APR_EOF;
+ }
+
+ if (APR_STATUS_IS_EAGAIN(status)) {
+ return status;
+ }
+
+ }
+
+ return APR_SUCCESS;
+}
+
+/* Validate that requests are sent and completed in the order of creation. */
+static void test_serf_connection_request_create(CuTest *tc)
+{
+ test_baton_t *tb;
+ serf_request_t *request1, *request2;
+ handler_baton_t handler_ctx, handler2_ctx;
+ apr_status_t status;
+ apr_pool_t *iter_pool;
+ apr_array_header_t *accepted_requests, *handled_requests, *sent_requests;
+ int i;
+ test_server_message_t message_list[] = {
+ {CHUNKED_REQUEST(1, "1")},
+ {CHUNKED_REQUEST(1, "2")},
+ };
+
+ test_server_action_t action_list[] = {
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ };
+ apr_pool_t *test_pool = test_setup();
+
+ accepted_requests = apr_array_make(test_pool, 2, sizeof(int));
+ sent_requests = apr_array_make(test_pool, 2, sizeof(int));
+ handled_requests = apr_array_make(test_pool, 2, sizeof(int));
+
+ /* Set up a test context with a server */
+ status = test_server_setup(&tb,
+ message_list, 2,
+ action_list, 2, 0, NULL,
+ test_pool);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ handler_ctx.method = "GET";
+ handler_ctx.path = "/";
+ handler_ctx.done = FALSE;
+
+ handler_ctx.acceptor = accept_response;
+ handler_ctx.acceptor_baton = NULL;
+ handler_ctx.handler = handle_response;
+ handler_ctx.req_id = 1;
+ handler_ctx.accepted_requests = accepted_requests;
+ handler_ctx.sent_requests = sent_requests;
+ handler_ctx.handled_requests = handled_requests;
+
+ request1 = serf_connection_request_create(tb->connection,
+ setup_request,
+ &handler_ctx);
+
+ handler2_ctx = handler_ctx;
+ handler2_ctx.req_id = 2;
+
+ request2 = serf_connection_request_create(tb->connection,
+ setup_request,
+ &handler2_ctx);
+
+ apr_pool_create(&iter_pool, test_pool);
+
+ while (!handler_ctx.done || !handler2_ctx.done)
+ {
+ apr_pool_clear(iter_pool);
+
+ status = test_server_run(tb->serv_ctx, 0, iter_pool);
+ if (APR_STATUS_IS_TIMEUP(status))
+ status = APR_SUCCESS;
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ status = serf_context_run(tb->context, 0, iter_pool);
+ if (APR_STATUS_IS_TIMEUP(status))
+ status = APR_SUCCESS;
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+ }
+ apr_pool_destroy(iter_pool);
+
+ /* Check that all requests were received */
+ CuAssertIntEquals(tc, 2, sent_requests->nelts);
+ CuAssertIntEquals(tc, 2, accepted_requests->nelts);
+ CuAssertIntEquals(tc, 2, handled_requests->nelts);
+
+ /* Check that the requests were sent in the order we created them */
+ for (i = 0; i < sent_requests->nelts; i++) {
+ int req_nr = APR_ARRAY_IDX(sent_requests, i, int);
+ CuAssertIntEquals(tc, i + 1, req_nr);
+ }
+
+ /* Check that the requests were received in the order we created them */
+ for (i = 0; i < handled_requests->nelts; i++) {
+ int req_nr = APR_ARRAY_IDX(handled_requests, i, int);
+ CuAssertIntEquals(tc, i + 1, req_nr);
+ }
+
+ test_server_teardown(tb, test_pool);
+ test_teardown(test_pool);
+}
+
+/* Validate that priority requests are sent and completed before normal
+ requests. */
+static void test_serf_connection_priority_request_create(CuTest *tc)
+{
+ test_baton_t *tb;
+ serf_request_t *request1, *request2, *request3;
+ handler_baton_t handler_ctx, handler2_ctx, handler3_ctx;
+ apr_status_t status;
+ apr_pool_t *iter_pool;
+ apr_array_header_t *accepted_requests, *handled_requests, *sent_requests;
+ int i;
+
+ test_server_message_t message_list[] = {
+ {CHUNKED_REQUEST(1, "1")},
+ {CHUNKED_REQUEST(1, "2")},
+ {CHUNKED_REQUEST(1, "3")},
+ };
+
+ test_server_action_t action_list[] = {
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ };
+
+ apr_pool_t *test_pool = test_setup();
+
+ accepted_requests = apr_array_make(test_pool, 3, sizeof(int));
+ sent_requests = apr_array_make(test_pool, 3, sizeof(int));
+ handled_requests = apr_array_make(test_pool, 3, sizeof(int));
+
+ /* Set up a test context with a server */
+ status = test_server_setup(&tb,
+ message_list, 3,
+ action_list, 3, 0, NULL,
+ test_pool);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ handler_ctx.method = "GET";
+ handler_ctx.path = "/";
+ handler_ctx.done = FALSE;
+
+ handler_ctx.acceptor = accept_response;
+ handler_ctx.acceptor_baton = NULL;
+ handler_ctx.handler = handle_response;
+ handler_ctx.req_id = 2;
+ handler_ctx.accepted_requests = accepted_requests;
+ handler_ctx.sent_requests = sent_requests;
+ handler_ctx.handled_requests = handled_requests;
+
+ request1 = serf_connection_request_create(tb->connection,
+ setup_request,
+ &handler_ctx);
+
+ handler2_ctx = handler_ctx;
+ handler2_ctx.req_id = 3;
+
+ request2 = serf_connection_request_create(tb->connection,
+ setup_request,
+ &handler2_ctx);
+ handler3_ctx = handler_ctx;
+ handler3_ctx.req_id = 1;
+
+ request3 = serf_connection_priority_request_create(tb->connection,
+ setup_request,
+ &handler3_ctx);
+
+ apr_pool_create(&iter_pool, test_pool);
+
+ while (!handler_ctx.done || !handler2_ctx.done || !handler3_ctx.done)
+ {
+ apr_pool_clear(iter_pool);
+
+ status = test_server_run(tb->serv_ctx, 0, iter_pool);
+ if (APR_STATUS_IS_TIMEUP(status))
+ status = APR_SUCCESS;
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ status = serf_context_run(tb->context, 0, iter_pool);
+ if (APR_STATUS_IS_TIMEUP(status))
+ status = APR_SUCCESS;
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ /* Debugging purposes only! */
+ serf_debug__closed_conn(tb->bkt_alloc);
+ }
+ apr_pool_destroy(iter_pool);
+
+ /* Check that all requests were received */
+ CuAssertIntEquals(tc, 3, sent_requests->nelts);
+ CuAssertIntEquals(tc, 3, accepted_requests->nelts);
+ CuAssertIntEquals(tc, 3, handled_requests->nelts);
+
+ /* Check that the requests were sent in the order we created them */
+ for (i = 0; i < sent_requests->nelts; i++) {
+ int req_nr = APR_ARRAY_IDX(sent_requests, i, int);
+ CuAssertIntEquals(tc, i + 1, req_nr);
+ }
+
+ /* Check that the requests were received in the order we created them */
+ for (i = 0; i < handled_requests->nelts; i++) {
+ int req_nr = APR_ARRAY_IDX(handled_requests, i, int);
+ CuAssertIntEquals(tc, i + 1, req_nr);
+ }
+
+ test_server_teardown(tb, test_pool);
+ test_teardown(test_pool);
+}
+
+/* Test that serf correctly handles the 'Connection:close' header when the
+ server is planning to close the connection. */
+#define NUM_REQUESTS 10
+static void test_serf_closed_connection(CuTest *tc)
+{
+ test_baton_t *tb;
+ apr_array_header_t *accepted_requests, *handled_requests, *sent_requests;
+ apr_status_t status;
+ handler_baton_t handler_ctx[NUM_REQUESTS];
+ int done = FALSE, i;
+
+ test_server_message_t message_list[] = {
+ {CHUNKED_REQUEST(1, "1")},
+ {CHUNKED_REQUEST(1, "2")},
+ {CHUNKED_REQUEST(1, "3")},
+ {CHUNKED_REQUEST(1, "4")},
+ {CHUNKED_REQUEST(1, "5")},
+ {CHUNKED_REQUEST(1, "6")},
+ {CHUNKED_REQUEST(1, "7")},
+ {CHUNKED_REQUEST(1, "8")},
+ {CHUNKED_REQUEST(1, "9")},
+ {CHUNKED_REQUEST(2, "10")}
+ };
+
+ test_server_action_t action_list[] = {
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND,
+ "HTTP/1.1 200 OK" CRLF
+ "Transfer-Encoding: chunked" CRLF
+ "Connection: close" CRLF
+ CRLF
+ "0" CRLF
+ CRLF
+ },
+ {SERVER_IGNORE_AND_KILL_CONNECTION},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND,
+ "HTTP/1.1 200 OK" CRLF
+ "Transfer-Encoding: chunked" CRLF
+ "Connection: close" CRLF
+ CRLF
+ "0" CRLF
+ CRLF
+ },
+ {SERVER_IGNORE_AND_KILL_CONNECTION},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ };
+
+ apr_pool_t *test_pool = test_setup();
+
+ accepted_requests = apr_array_make(test_pool, NUM_REQUESTS, sizeof(int));
+ sent_requests = apr_array_make(test_pool, NUM_REQUESTS, sizeof(int));
+ handled_requests = apr_array_make(test_pool, NUM_REQUESTS, sizeof(int));
+
+ /* Set up a test context with a server. */
+ status = test_server_setup(&tb,
+ message_list, 10,
+ action_list, 12,
+ 0,
+ NULL,
+ test_pool);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ for (i = 0 ; i < NUM_REQUESTS ; i++) {
+ /* Send some requests on the connections */
+ handler_ctx[i].method = "GET";
+ handler_ctx[i].path = "/";
+ handler_ctx[i].done = FALSE;
+
+ handler_ctx[i].acceptor = accept_response;
+ handler_ctx[i].acceptor_baton = NULL;
+ handler_ctx[i].handler = handle_response;
+ handler_ctx[i].req_id = i+1;
+ handler_ctx[i].accepted_requests = accepted_requests;
+ handler_ctx[i].sent_requests = sent_requests;
+ handler_ctx[i].handled_requests = handled_requests;
+ handler_ctx[i].tb = tb;
+
+ serf_connection_request_create(tb->connection,
+ setup_request,
+ &handler_ctx[i]);
+ }
+
+ while (1) {
+ status = test_server_run(tb->serv_ctx, 0, test_pool);
+ if (APR_STATUS_IS_TIMEUP(status))
+ status = APR_SUCCESS;
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ status = serf_context_run(tb->context, 0, test_pool);
+ if (APR_STATUS_IS_TIMEUP(status))
+ status = APR_SUCCESS;
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ /* Debugging purposes only! */
+ serf_debug__closed_conn(tb->bkt_alloc);
+
+ done = TRUE;
+ for (i = 0 ; i < NUM_REQUESTS ; i++)
+ if (handler_ctx[i].done == FALSE) {
+ done = FALSE;
+ break;
+ }
+ if (done)
+ break;
+ }
+
+ /* Check that all requests were received */
+ CuAssertTrue(tc, sent_requests->nelts >= NUM_REQUESTS);
+ CuAssertIntEquals(tc, NUM_REQUESTS, accepted_requests->nelts);
+ CuAssertIntEquals(tc, NUM_REQUESTS, handled_requests->nelts);
+
+ /* Cleanup */
+ test_server_teardown(tb, test_pool);
+ test_teardown(test_pool);
+}
+#undef NUM_REQUESTS
+
+/* Test if serf is sending the request to the proxy, not to the server
+ directly. */
+static void test_serf_setup_proxy(CuTest *tc)
+{
+ test_baton_t *tb;
+ serf_request_t *request;
+ handler_baton_t handler_ctx;
+ apr_status_t status;
+ apr_pool_t *iter_pool;
+ apr_array_header_t *accepted_requests, *handled_requests, *sent_requests;
+ int i;
+ int numrequests = 1;
+
+ test_server_message_t message_list[] = {
+ {"GET http://localhost:" SERV_PORT_STR " HTTP/1.1" CRLF\
+ "Host: localhost:" SERV_PORT_STR CRLF\
+ "Transfer-Encoding: chunked" CRLF\
+ CRLF\
+ "1" CRLF\
+ "1" CRLF\
+ "0" CRLF\
+ CRLF}
+ };
+
+ test_server_action_t action_list_proxy[] = {
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ };
+
+ apr_pool_t *test_pool = test_setup();
+
+ accepted_requests = apr_array_make(test_pool, numrequests, sizeof(int));
+ sent_requests = apr_array_make(test_pool, numrequests, sizeof(int));
+ handled_requests = apr_array_make(test_pool, numrequests, sizeof(int));
+
+ /* Set up a test context with a server, no messages expected. */
+ status = test_server_proxy_setup(&tb,
+ /* server messages and actions */
+ NULL, 0,
+ NULL, 0,
+ /* server messages and actions */
+ message_list, 1,
+ action_list_proxy, 1,
+ 0,
+ NULL, test_pool);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ handler_ctx.method = "GET";
+ handler_ctx.path = "/";
+ handler_ctx.done = FALSE;
+
+ handler_ctx.acceptor = accept_response;
+ handler_ctx.acceptor_baton = NULL;
+ handler_ctx.handler = handle_response;
+ handler_ctx.req_id = 1;
+ handler_ctx.accepted_requests = accepted_requests;
+ handler_ctx.sent_requests = sent_requests;
+ handler_ctx.handled_requests = handled_requests;
+
+ request = serf_connection_request_create(tb->connection,
+ setup_request,
+ &handler_ctx);
+
+ apr_pool_create(&iter_pool, test_pool);
+
+ while (!handler_ctx.done)
+ {
+ apr_pool_clear(iter_pool);
+
+ status = test_server_run(tb->serv_ctx, 0, iter_pool);
+ if (APR_STATUS_IS_TIMEUP(status))
+ status = APR_SUCCESS;
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ status = test_server_run(tb->proxy_ctx, 0, iter_pool);
+ if (APR_STATUS_IS_TIMEUP(status))
+ status = APR_SUCCESS;
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ status = serf_context_run(tb->context, 0, iter_pool);
+ if (APR_STATUS_IS_TIMEUP(status))
+ status = APR_SUCCESS;
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ /* Debugging purposes only! */
+ serf_debug__closed_conn(tb->bkt_alloc);
+ }
+ apr_pool_destroy(iter_pool);
+
+ /* Check that all requests were received */
+ CuAssertIntEquals(tc, numrequests, sent_requests->nelts);
+ CuAssertIntEquals(tc, numrequests, accepted_requests->nelts);
+ CuAssertIntEquals(tc, numrequests, handled_requests->nelts);
+
+ /* Check that the requests were sent in the order we created them */
+ for (i = 0; i < sent_requests->nelts; i++) {
+ int req_nr = APR_ARRAY_IDX(sent_requests, i, int);
+ CuAssertIntEquals(tc, i + 1, req_nr);
+ }
+
+ /* Check that the requests were received in the order we created them */
+ for (i = 0; i < handled_requests->nelts; i++) {
+ int req_nr = APR_ARRAY_IDX(handled_requests, i, int);
+ CuAssertIntEquals(tc, i + 1, req_nr);
+ }
+
+ test_server_teardown(tb, test_pool);
+ test_teardown(test_pool);
+}
+
+/*****************************************************************************
+ * Test if we can make serf send requests one by one.
+ *****************************************************************************/
+
+/* Resend the first request 4 times by reducing the pipeline bandwidth to
+ one request at a time, and by adding the first request again at the start of
+ the outgoing queue. */
+static apr_status_t
+handle_response_keepalive_limit(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ handler_baton_t *ctx = handler_baton;
+
+ if (! response) {
+ return APR_SUCCESS;
+ }
+
+ while (1) {
+ apr_status_t status;
+ const char *data;
+ apr_size_t len;
+
+ status = serf_bucket_read(response, 2048, &data, &len);
+ if (SERF_BUCKET_READ_ERROR(status)) {
+ return status;
+ }
+
+ if (APR_STATUS_IS_EOF(status)) {
+ APR_ARRAY_PUSH(ctx->handled_requests, int) = ctx->req_id;
+ ctx->done = TRUE;
+ if (ctx->req_id == 1 && ctx->handled_requests->nelts < 3) {
+ serf_connection_priority_request_create(ctx->tb->connection,
+ setup_request,
+ ctx);
+ ctx->done = FALSE;
+ }
+ return APR_EOF;
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+#define SEND_REQUESTS 5
+#define RCVD_REQUESTS 7
+static void test_keepalive_limit_one_by_one(CuTest *tc)
+{
+ test_baton_t *tb;
+ apr_array_header_t *accepted_requests, *handled_requests, *sent_requests;
+ apr_status_t status;
+ handler_baton_t handler_ctx[SEND_REQUESTS];
+ int done = FALSE, i;
+
+ test_server_message_t message_list[] = {
+ {CHUNKED_REQUEST(1, "1")},
+ {CHUNKED_REQUEST(1, "1")},
+ {CHUNKED_REQUEST(1, "1")},
+ {CHUNKED_REQUEST(1, "2")},
+ {CHUNKED_REQUEST(1, "3")},
+ {CHUNKED_REQUEST(1, "4")},
+ {CHUNKED_REQUEST(1, "5")},
+ };
+
+ test_server_action_t action_list[] = {
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ };
+
+ apr_pool_t *test_pool = test_setup();
+
+ accepted_requests = apr_array_make(test_pool, RCVD_REQUESTS, sizeof(int));
+ sent_requests = apr_array_make(test_pool, RCVD_REQUESTS, sizeof(int));
+ handled_requests = apr_array_make(test_pool, RCVD_REQUESTS, sizeof(int));
+
+ /* Set up a test context with a server. */
+ status = test_server_setup(&tb,
+ message_list, 7,
+ action_list, 7, 0, NULL,
+ test_pool);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ for (i = 0 ; i < SEND_REQUESTS ; i++) {
+ /* Send some requests on the connections */
+ handler_ctx[i].method = "GET";
+ handler_ctx[i].path = "/";
+ handler_ctx[i].done = FALSE;
+
+ handler_ctx[i].acceptor = accept_response;
+ handler_ctx[i].acceptor_baton = NULL;
+ handler_ctx[i].handler = handle_response_keepalive_limit;
+ handler_ctx[i].req_id = i+1;
+ handler_ctx[i].accepted_requests = accepted_requests;
+ handler_ctx[i].sent_requests = sent_requests;
+ handler_ctx[i].handled_requests = handled_requests;
+ handler_ctx[i].tb = tb;
+
+ serf_connection_request_create(tb->connection,
+ setup_request,
+ &handler_ctx[i]);
+ serf_connection_set_max_outstanding_requests(tb->connection, 1);
+ }
+
+ while (1) {
+ status = test_server_run(tb->serv_ctx, 0, test_pool);
+ if (APR_STATUS_IS_TIMEUP(status))
+ status = APR_SUCCESS;
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ status = serf_context_run(tb->context, 0, test_pool);
+ if (APR_STATUS_IS_TIMEUP(status))
+ status = APR_SUCCESS;
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ /* Debugging purposes only! */
+ serf_debug__closed_conn(tb->bkt_alloc);
+
+ done = TRUE;
+ for (i = 0 ; i < SEND_REQUESTS ; i++)
+ if (handler_ctx[i].done == FALSE) {
+ done = FALSE;
+ break;
+ }
+ if (done)
+ break;
+ }
+
+ /* Check that all requests were received */
+ CuAssertIntEquals(tc, RCVD_REQUESTS, sent_requests->nelts);
+ CuAssertIntEquals(tc, RCVD_REQUESTS, accepted_requests->nelts);
+ CuAssertIntEquals(tc, RCVD_REQUESTS, handled_requests->nelts);
+
+ /* Cleanup */
+ test_server_teardown(tb, test_pool);
+ test_teardown(test_pool);
+}
+#undef SEND_REQUESTS
+#undef RCVD_REQUESTS
+
+/*****************************************************************************
+ * Test if we can make serf first send requests one by one, and then change
+ * back to burst mode.
+ *****************************************************************************/
+#define SEND_REQUESTS 5
+#define RCVD_REQUESTS 7
+/* Resend the first request 2 times by reducing the pipeline bandwidth to
+ one request at a time, and by adding the first request again at the start of
+ the outgoing queue. */
+static apr_status_t
+handle_response_keepalive_limit_burst(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ handler_baton_t *ctx = handler_baton;
+
+ if (! response) {
+ return APR_SUCCESS;
+ }
+
+ while (1) {
+ apr_status_t status;
+ const char *data;
+ apr_size_t len;
+
+ status = serf_bucket_read(response, 2048, &data, &len);
+ if (SERF_BUCKET_READ_ERROR(status)) {
+ return status;
+ }
+
+ if (APR_STATUS_IS_EOF(status)) {
+ APR_ARRAY_PUSH(ctx->handled_requests, int) = ctx->req_id;
+ ctx->done = TRUE;
+ if (ctx->req_id == 1 && ctx->handled_requests->nelts < 3) {
+ serf_connection_priority_request_create(ctx->tb->connection,
+ setup_request,
+ ctx);
+ ctx->done = FALSE;
+ }
+ else {
+ /* No more one-by-one. */
+ serf_connection_set_max_outstanding_requests(ctx->tb->connection,
+ 0);
+ }
+ return APR_EOF;
+ }
+
+ if (APR_STATUS_IS_EAGAIN(status)) {
+ return status;
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+static void test_keepalive_limit_one_by_one_and_burst(CuTest *tc)
+{
+ test_baton_t *tb;
+ apr_array_header_t *accepted_requests, *handled_requests, *sent_requests;
+ apr_status_t status;
+ handler_baton_t handler_ctx[SEND_REQUESTS];
+ int done = FALSE, i;
+
+ test_server_message_t message_list[] = {
+ {CHUNKED_REQUEST(1, "1")},
+ {CHUNKED_REQUEST(1, "1")},
+ {CHUNKED_REQUEST(1, "1")},
+ {CHUNKED_REQUEST(1, "2")},
+ {CHUNKED_REQUEST(1, "3")},
+ {CHUNKED_REQUEST(1, "4")},
+ {CHUNKED_REQUEST(1, "5")},
+ };
+
+ test_server_action_t action_list[] = {
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ };
+
+ apr_pool_t *test_pool = test_setup();
+
+ accepted_requests = apr_array_make(test_pool, RCVD_REQUESTS, sizeof(int));
+ sent_requests = apr_array_make(test_pool, RCVD_REQUESTS, sizeof(int));
+ handled_requests = apr_array_make(test_pool, RCVD_REQUESTS, sizeof(int));
+
+ /* Set up a test context with a server. */
+ status = test_server_setup(&tb,
+ message_list, 7,
+ action_list, 7, 0, NULL,
+ test_pool);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ for (i = 0 ; i < SEND_REQUESTS ; i++) {
+ /* Send some requests on the connections */
+ handler_ctx[i].method = "GET";
+ handler_ctx[i].path = "/";
+ handler_ctx[i].done = FALSE;
+
+ handler_ctx[i].acceptor = accept_response;
+ handler_ctx[i].acceptor_baton = NULL;
+ handler_ctx[i].handler = handle_response_keepalive_limit_burst;
+ handler_ctx[i].req_id = i+1;
+ handler_ctx[i].accepted_requests = accepted_requests;
+ handler_ctx[i].sent_requests = sent_requests;
+ handler_ctx[i].handled_requests = handled_requests;
+ handler_ctx[i].tb = tb;
+
+ serf_connection_request_create(tb->connection,
+ setup_request,
+ &handler_ctx[i]);
+ serf_connection_set_max_outstanding_requests(tb->connection, 1);
+ }
+
+ while (1) {
+ status = test_server_run(tb->serv_ctx, 0, test_pool);
+ if (APR_STATUS_IS_TIMEUP(status))
+ status = APR_SUCCESS;
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ status = serf_context_run(tb->context, 0, test_pool);
+ if (APR_STATUS_IS_TIMEUP(status))
+ status = APR_SUCCESS;
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ /* Debugging purposes only! */
+ serf_debug__closed_conn(tb->bkt_alloc);
+
+ done = TRUE;
+ for (i = 0 ; i < SEND_REQUESTS ; i++)
+ if (handler_ctx[i].done == FALSE) {
+ done = FALSE;
+ break;
+ }
+ if (done)
+ break;
+ }
+
+ /* Check that all requests were received */
+ CuAssertIntEquals(tc, RCVD_REQUESTS, sent_requests->nelts);
+ CuAssertIntEquals(tc, RCVD_REQUESTS, accepted_requests->nelts);
+ CuAssertIntEquals(tc, RCVD_REQUESTS, handled_requests->nelts);
+
+ /* Cleanup */
+ test_server_teardown(tb, test_pool);
+ test_teardown(test_pool);
+}
+#undef SEND_REQUESTS
+#undef RCVD_REQUESTS
+
+#define NUM_REQUESTS 5
+typedef struct {
+ apr_off_t read;
+ apr_off_t written;
+} progress_baton_t;
+
+static void
+progress_cb(void *progress_baton, apr_off_t read, apr_off_t written)
+{
+ test_baton_t *tb = progress_baton;
+ progress_baton_t *pb = tb->user_baton;
+
+ pb->read = read;
+ pb->written = written;
+}
+
+static apr_status_t progress_conn_setup(apr_socket_t *skt,
+ serf_bucket_t **input_bkt,
+ serf_bucket_t **output_bkt,
+ void *setup_baton,
+ apr_pool_t *pool)
+{
+ test_baton_t *tb = setup_baton;
+ *input_bkt = serf_context_bucket_socket_create(tb->context, skt, tb->bkt_alloc);
+ return APR_SUCCESS;
+}
+
+static void test_serf_progress_callback(CuTest *tc)
+{
+ test_baton_t *tb;
+ apr_array_header_t *accepted_requests, *handled_requests, *sent_requests;
+ apr_status_t status;
+ handler_baton_t handler_ctx[NUM_REQUESTS];
+ int done = FALSE, i;
+ progress_baton_t *pb;
+
+ test_server_message_t message_list[] = {
+ {CHUNKED_REQUEST(1, "1")},
+ {CHUNKED_REQUEST(1, "2")},
+ {CHUNKED_REQUEST(1, "3")},
+ {CHUNKED_REQUEST(1, "4")},
+ {CHUNKED_REQUEST(1, "5")},
+ };
+
+ test_server_action_t action_list[] = {
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND, CHUNKED_RESPONSE(1, "2")},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ {SERVER_RESPOND, CHUNKED_EMPTY_RESPONSE},
+ };
+
+ apr_pool_t *test_pool = test_setup();
+
+ accepted_requests = apr_array_make(test_pool, NUM_REQUESTS, sizeof(int));
+ sent_requests = apr_array_make(test_pool, NUM_REQUESTS, sizeof(int));
+ handled_requests = apr_array_make(test_pool, NUM_REQUESTS, sizeof(int));
+
+ /* Set up a test context with a server. */
+ status = test_server_setup(&tb,
+ message_list, 5,
+ action_list, 5, 0,
+ progress_conn_setup, test_pool);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ /* Set up the progress callback. */
+ pb = apr_pcalloc(test_pool, sizeof(*pb));
+ tb->user_baton = pb;
+ serf_context_set_progress_cb(tb->context, progress_cb, tb);
+
+ for (i = 0 ; i < NUM_REQUESTS ; i++) {
+ /* Send some requests on the connections */
+ handler_ctx[i].method = "GET";
+ handler_ctx[i].path = "/";
+ handler_ctx[i].done = FALSE;
+
+ handler_ctx[i].acceptor = accept_response;
+ handler_ctx[i].acceptor_baton = NULL;
+ handler_ctx[i].handler = handle_response;
+ handler_ctx[i].req_id = i+1;
+ handler_ctx[i].accepted_requests = accepted_requests;
+ handler_ctx[i].sent_requests = sent_requests;
+ handler_ctx[i].handled_requests = handled_requests;
+ handler_ctx[i].tb = tb;
+
+ serf_connection_request_create(tb->connection,
+ setup_request,
+ &handler_ctx[i]);
+ }
+
+ while (1) {
+ status = test_server_run(tb->serv_ctx, 0, test_pool);
+ if (APR_STATUS_IS_TIMEUP(status))
+ status = APR_SUCCESS;
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ status = serf_context_run(tb->context, 0, test_pool);
+ if (APR_STATUS_IS_TIMEUP(status))
+ status = APR_SUCCESS;
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ /* Debugging purposes only! */
+ serf_debug__closed_conn(tb->bkt_alloc);
+
+ done = TRUE;
+ for (i = 0 ; i < NUM_REQUESTS ; i++)
+ if (handler_ctx[i].done == FALSE) {
+ done = FALSE;
+ break;
+ }
+ if (done)
+ break;
+ }
+
+ /* Check that all requests were received */
+ CuAssertTrue(tc, sent_requests->nelts >= NUM_REQUESTS);
+ CuAssertIntEquals(tc, NUM_REQUESTS, accepted_requests->nelts);
+ CuAssertIntEquals(tc, NUM_REQUESTS, handled_requests->nelts);
+
+ /* Check that progress was reported. */
+ CuAssertTrue(tc, pb->written > 0);
+ CuAssertTrue(tc, pb->read > 0);
+
+ /* Cleanup */
+ test_server_teardown(tb, test_pool);
+ test_teardown(test_pool);
+}
+#undef NUM_REQUESTS
+
+CuSuite *test_context(void)
+{
+ CuSuite *suite = CuSuiteNew();
+
+ SUITE_ADD_TEST(suite, test_serf_connection_request_create);
+ SUITE_ADD_TEST(suite, test_serf_connection_priority_request_create);
+ SUITE_ADD_TEST(suite, test_serf_closed_connection);
+ SUITE_ADD_TEST(suite, test_serf_setup_proxy);
+ SUITE_ADD_TEST(suite, test_keepalive_limit_one_by_one);
+ SUITE_ADD_TEST(suite, test_keepalive_limit_one_by_one_and_burst);
+ SUITE_ADD_TEST(suite, test_serf_progress_callback);
+
+ return suite;
+}
diff --git a/test/test_serf.h b/test/test_serf.h
new file mode 100644
index 0000000..454dfcc
--- /dev/null
+++ b/test/test_serf.h
@@ -0,0 +1,126 @@
+/* Copyright 2002-2007 Justin Erenkrantz and Greg Stein
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef TEST_SERF_H
+#define TEST_SERF_H
+
+#include "CuTest.h"
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_uri.h>
+
+#include "serf.h"
+#include "server/test_server.h"
+
+/** These macros are provided by APR itself from version 1.3.
+ * Definitions are provided here for when using older versions of APR.
+ */
+
+/** index into an apr_array_header_t */
+#ifndef APR_ARRAY_IDX
+#define APR_ARRAY_IDX(ary,i,type) (((type *)(ary)->elts)[i])
+#endif
+
+/** easier array-pushing syntax */
+#ifndef APR_ARRAY_PUSH
+#define APR_ARRAY_PUSH(ary,type) (*((type *)apr_array_push(ary)))
+#endif
+
+/* CuTest declarations */
+CuSuite *getsuite(void);
+
+CuSuite *test_context(void);
+CuSuite *test_buckets(void);
+CuSuite *test_ssl(void);
+
+/* Test setup declarations */
+
+#define CRLF "\r\n"
+
+#define CHUNKED_REQUEST(len, body)\
+ "GET / HTTP/1.1" CRLF\
+ "Host: localhost:12345" CRLF\
+ "Transfer-Encoding: chunked" CRLF\
+ CRLF\
+ #len CRLF\
+ body CRLF\
+ "0" CRLF\
+ CRLF
+
+#define CHUNKED_RESPONSE(len, body)\
+ "HTTP/1.1 200 OK" CRLF\
+ "Transfer-Encoding: chunked" CRLF\
+ CRLF\
+ #len CRLF\
+ body CRLF\
+ "0" CRLF\
+ CRLF
+
+#define CHUNKED_EMPTY_RESPONSE\
+ "HTTP/1.1 200 OK" CRLF\
+ "Transfer-Encoding: chunked" CRLF\
+ CRLF\
+ "0" CRLF\
+ CRLF
+
+typedef struct {
+ /* Pool for resource allocation. */
+ apr_pool_t *pool;
+
+ serf_context_t *context;
+ serf_connection_t *connection;
+ serf_bucket_alloc_t *bkt_alloc;
+
+ serv_ctx_t *serv_ctx;
+ apr_sockaddr_t *serv_addr;
+
+ serv_ctx_t *proxy_ctx;
+ apr_sockaddr_t *proxy_addr;
+
+ /* An extra baton which can be freely used by tests. */
+ void *user_baton;
+
+} test_baton_t;
+
+apr_status_t test_server_setup(test_baton_t **tb_p,
+ test_server_message_t *message_list,
+ apr_size_t message_count,
+ test_server_action_t *action_list,
+ apr_size_t action_count,
+ apr_int32_t options,
+ serf_connection_setup_t conn_setup,
+ apr_pool_t *pool);
+
+apr_status_t test_server_proxy_setup(
+ test_baton_t **tb_p,
+ test_server_message_t *serv_message_list,
+ apr_size_t serv_message_count,
+ test_server_action_t *serv_action_list,
+ apr_size_t serv_action_count,
+ test_server_message_t *proxy_message_list,
+ apr_size_t proxy_message_count,
+ test_server_action_t *proxy_action_list,
+ apr_size_t proxy_action_count,
+ apr_int32_t options,
+ serf_connection_setup_t conn_setup,
+ apr_pool_t *pool);
+
+apr_status_t test_server_teardown(test_baton_t *tb, apr_pool_t *pool);
+
+apr_pool_t *test_setup(void);
+void test_teardown(apr_pool_t *test_pool);
+
+#endif /* TEST_SERF_H */
diff --git a/test/test_ssl.c b/test/test_ssl.c
new file mode 100644
index 0000000..81b939d
--- /dev/null
+++ b/test/test_ssl.c
@@ -0,0 +1,112 @@
+/* Copyright 2008 Justin Erenkrantz and Greg Stein
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <apr.h>
+#include <apr_pools.h>
+#include <apr_strings.h>
+#include <apr_env.h>
+
+#include "serf.h"
+#include "serf_bucket_types.h"
+
+#include "test_serf.h"
+
+#if defined(WIN32) && defined(_DEBUG)
+/* Include this file to allow running a Debug build of serf with a Release
+ build of OpenSSL. */
+#include <openssl/applink.c>
+#endif
+
+/* Test setting up the openssl library. */
+static void test_ssl_init(CuTest *tc)
+{
+ serf_bucket_t *bkt, *stream;
+ serf_ssl_context_t *ssl_context;
+ apr_status_t status;
+
+ apr_pool_t *test_pool = test_setup();
+ serf_bucket_alloc_t *alloc = serf_bucket_allocator_create(test_pool, NULL,
+ NULL);
+
+ stream = SERF_BUCKET_SIMPLE_STRING("", alloc);
+
+ bkt = serf_bucket_ssl_decrypt_create(stream, NULL,
+ alloc);
+ ssl_context = serf_bucket_ssl_decrypt_context_get(bkt);
+
+ bkt = serf_bucket_ssl_encrypt_create(stream, ssl_context,
+ alloc);
+
+ status = serf_ssl_use_default_certificates(ssl_context);
+
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+ test_teardown(test_pool);
+}
+
+/* Test that loading a custom CA certificate file works. */
+static void test_ssl_load_cert_file(CuTest *tc)
+{
+ serf_ssl_certificate_t *cert = NULL;
+
+ apr_pool_t *test_pool = test_setup();
+ apr_status_t status = serf_ssl_load_cert_file(&cert, "test/serftestca.pem",
+ test_pool);
+
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+ CuAssertPtrNotNull(tc, cert);
+ test_teardown(test_pool);
+}
+
+/* Test that reading a custom CA certificate file works. */
+static void test_ssl_cert_subject(CuTest *tc)
+{
+ apr_hash_t *subject;
+ serf_ssl_certificate_t *cert = NULL;
+ apr_status_t status;
+
+ apr_pool_t *test_pool = test_setup();
+
+ status = serf_ssl_load_cert_file(&cert, "test/serftestca.pem", test_pool);
+
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+ CuAssertPtrNotNull(tc, cert);
+
+ subject = serf_ssl_cert_subject(cert, test_pool);
+ CuAssertStrEquals(tc, "Test Suite",
+ apr_hash_get(subject, "OU", APR_HASH_KEY_STRING));
+ CuAssertStrEquals(tc, "In Serf we trust, Inc.",
+ apr_hash_get(subject, "O", APR_HASH_KEY_STRING));
+ CuAssertStrEquals(tc, "Mechelen",
+ apr_hash_get(subject, "L", APR_HASH_KEY_STRING));
+ CuAssertStrEquals(tc, "Antwerp",
+ apr_hash_get(subject, "ST", APR_HASH_KEY_STRING));
+ CuAssertStrEquals(tc, "BE",
+ apr_hash_get(subject, "C", APR_HASH_KEY_STRING));
+ CuAssertStrEquals(tc, "serf@example.com",
+ apr_hash_get(subject, "E", APR_HASH_KEY_STRING));
+
+ test_teardown(test_pool);
+}
+
+CuSuite *test_ssl(void)
+{
+ CuSuite *suite = CuSuiteNew();
+
+ SUITE_ADD_TEST(suite, test_ssl_init);
+ SUITE_ADD_TEST(suite, test_ssl_load_cert_file);
+ SUITE_ADD_TEST(suite, test_ssl_cert_subject);
+
+ return suite;
+}
diff --git a/test/test_util.c b/test/test_util.c
new file mode 100644
index 0000000..9496195
--- /dev/null
+++ b/test/test_util.c
@@ -0,0 +1,214 @@
+/* Copyright 2002-2007 Justin Erenkrantz and Greg Stein
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "apr.h"
+#include "apr_pools.h"
+#include <stdlib.h>
+
+#include "serf.h"
+
+#include "test_serf.h"
+#include "server/test_server.h"
+
+
+/*****************************************************************************/
+/* Server setup function(s)
+ */
+
+#define SERV_URL "http://localhost:" SERV_PORT_STR
+
+static apr_status_t default_server_address(apr_sockaddr_t **address,
+ apr_pool_t *pool)
+{
+ return apr_sockaddr_info_get(address,
+ "localhost", APR_UNSPEC, SERV_PORT, 0,
+ pool);
+}
+
+static apr_status_t default_proxy_address(apr_sockaddr_t **address,
+ apr_pool_t *pool)
+{
+ return apr_sockaddr_info_get(address,
+ "localhost", APR_UNSPEC, PROXY_PORT, 0,
+ pool);
+}
+
+/* Default implementation of a serf_connection_closed_t callback. */
+static void default_closed_connection(serf_connection_t *conn,
+ void *closed_baton,
+ apr_status_t why,
+ apr_pool_t *pool)
+{
+ if (why) {
+ abort();
+ }
+}
+
+/* Default implementation of a serf_connection_setup_t callback. */
+static apr_status_t default_conn_setup(apr_socket_t *skt,
+ serf_bucket_t **input_bkt,
+ serf_bucket_t **output_bkt,
+ void *setup_baton,
+ apr_pool_t *pool)
+{
+ test_baton_t *ctx = setup_baton;
+
+ *input_bkt = serf_bucket_socket_create(skt, ctx->bkt_alloc);
+ return APR_SUCCESS;
+}
+
+
+static apr_status_t setup(test_baton_t **tb_p,
+ serf_connection_setup_t conn_setup,
+ int use_proxy,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+ test_baton_t *tb;
+ apr_uri_t url;
+
+ tb = apr_pcalloc(pool, sizeof(*tb));
+ *tb_p = tb;
+
+ tb->pool = pool;
+ tb->context = serf_context_create(pool);
+ tb->bkt_alloc = serf_bucket_allocator_create(pool, NULL, NULL);
+
+ status = default_server_address(&tb->serv_addr, pool);
+ if (status != APR_SUCCESS)
+ return status;
+
+ if (use_proxy) {
+ status = default_proxy_address(&tb->proxy_addr, pool);
+ if (status != APR_SUCCESS)
+ return status;
+
+ /* Configure serf to use the proxy server */
+ serf_config_proxy(tb->context, tb->proxy_addr);
+ }
+
+ status = apr_uri_parse(pool, SERV_URL, &url);
+ if (status != APR_SUCCESS)
+ return status;
+
+ status = serf_connection_create2(&tb->connection, tb->context,
+ url,
+ conn_setup ? conn_setup :
+ default_conn_setup,
+ tb,
+ default_closed_connection,
+ tb,
+ pool);
+
+ return status;
+}
+
+
+
+apr_status_t test_server_setup(test_baton_t **tb_p,
+ test_server_message_t *message_list,
+ apr_size_t message_count,
+ test_server_action_t *action_list,
+ apr_size_t action_count,
+ apr_int32_t options,
+ serf_connection_setup_t conn_setup,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+ test_baton_t *tb;
+
+ status = setup(tb_p,
+ conn_setup,
+ FALSE,
+ pool);
+ if (status != APR_SUCCESS)
+ return status;
+
+ tb = *tb_p;
+
+ /* Prepare a server. */
+ status = test_start_server(&tb->serv_ctx, tb->serv_addr,
+ message_list, message_count,
+ action_list, action_count, options, pool);
+
+ return status;
+}
+
+apr_status_t
+test_server_proxy_setup(test_baton_t **tb_p,
+ test_server_message_t *serv_message_list,
+ apr_size_t serv_message_count,
+ test_server_action_t *serv_action_list,
+ apr_size_t serv_action_count,
+ test_server_message_t *proxy_message_list,
+ apr_size_t proxy_message_count,
+ test_server_action_t *proxy_action_list,
+ apr_size_t proxy_action_count,
+ apr_int32_t options,
+ serf_connection_setup_t conn_setup,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+ test_baton_t *tb;
+
+ status = setup(tb_p,
+ conn_setup,
+ TRUE,
+ pool);
+ if (status != APR_SUCCESS)
+ return status;
+
+ tb = *tb_p;
+
+ /* Prepare the server. */
+ status = test_start_server(&tb->serv_ctx, tb->serv_addr,
+ serv_message_list, serv_message_count,
+ serv_action_list, serv_action_count,
+ options, pool);
+ if (status != APR_SUCCESS)
+ return status;
+
+ /* Prepare the proxy. */
+ status = test_start_server(&tb->proxy_ctx, tb->proxy_addr,
+ proxy_message_list, proxy_message_count,
+ proxy_action_list, proxy_action_count,
+ options, pool);
+
+ return status;
+}
+
+apr_status_t test_server_teardown(test_baton_t *tb, apr_pool_t *pool)
+{
+ serf_connection_close(tb->connection);
+
+ if (tb->serv_ctx)
+ test_server_destroy(tb->serv_ctx, pool);
+ if (tb->proxy_ctx)
+ test_server_destroy(tb->proxy_ctx, pool);
+
+ return APR_SUCCESS;
+}
+
+apr_pool_t *test_setup(void)
+{
+ apr_pool_t *test_pool;
+ apr_pool_create(&test_pool, NULL);
+ return test_pool;
+}
+
+void test_teardown(apr_pool_t *test_pool)
+{
+ apr_pool_destroy(test_pool);
+}
diff --git a/test/testcases/chunked-empty.response b/test/testcases/chunked-empty.response
new file mode 100644
index 0000000..8794459
--- /dev/null
+++ b/test/testcases/chunked-empty.response
@@ -0,0 +1,11 @@
+HTTP/1.1 200 OK
+Date: Sat, 04 Sep 2004 07:57:30 GMT
+Server: Apache/2.0.49-dev
+Last-Modified: Fri, 20 Sep 2002 01:33:12 GMT
+ETag: "19f5cb-240-48f26600"
+Accept-Ranges: bytes
+Transfer-Encoding: chunked
+Content-Type: text/html; charset=ISO-8859-1
+
+0
+
diff --git a/test/testcases/chunked-trailers.response b/test/testcases/chunked-trailers.response
new file mode 100644
index 0000000..8d66687
--- /dev/null
+++ b/test/testcases/chunked-trailers.response
@@ -0,0 +1,14 @@
+HTTP/1.1 200 OK
+Transfer-Encoding: chunked
+
+1d
+this is 1 test.
+i am a test.
+f
+this is a test.
+2
+
+
+0
+Trailer-Test: f
+
diff --git a/test/testcases/chunked.response b/test/testcases/chunked.response
new file mode 100644
index 0000000..16c9d33
--- /dev/null
+++ b/test/testcases/chunked.response
@@ -0,0 +1,13 @@
+HTTP/1.1 200 OK
+Transfer-Encoding: chunked
+
+1d
+this is 1 test.
+i am a test.
+f
+this is a test.
+2
+
+
+0
+
diff --git a/test/testcases/deflate.response b/test/testcases/deflate.response
new file mode 100644
index 0000000..0c277bf
--- /dev/null
+++ b/test/testcases/deflate.response
Binary files differ
diff --git a/test/testcases/simple.request b/test/testcases/simple.request
new file mode 100644
index 0000000..faab00e
--- /dev/null
+++ b/test/testcases/simple.request
@@ -0,0 +1 @@
+this is a test.
diff --git a/test/testcases/simple.response b/test/testcases/simple.response
new file mode 100644
index 0000000..aa66cc3
--- /dev/null
+++ b/test/testcases/simple.response
@@ -0,0 +1,32 @@
+HTTP/1.1 200 OK
+Date: Sat, 04 Sep 2004 07:57:30 GMT
+Server: Apache/2.0.49-dev
+Last-Modified: Fri, 20 Sep 2002 01:33:12 GMT
+ETag: "19f5cb-240-48f26600"
+Accept-Ranges: bytes
+Content-Length: 599
+Content-Type: text/html; charset=ISO-8859-1
+
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+<title>scotch.ics.uci.edu</title>
+<!--base href="http://scotch.ics.uci.edu/" /-->
+<link href="default.css" rel="stylesheet" type="text/css" />
+</head>
+
+<body>
+
+<p>More to come!</p>
+
+<p><a href="manual/">Apache httpd 2.0 manual</a></p>
+
+<p><a href="CA.cert.pem">Trust our CA!</a></p>
+
+<p><img src="apache_pb.gif" alt="Powered by Apache!" /></p>
+
+</body>
+
+</html>