diff --git a/CMakeLists_note_server.txt b/CMakeLists_note_server.txt new file mode 100644 index 0000000000..fd1c5a70d9 --- /dev/null +++ b/CMakeLists_note_server.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.14.0 FATAL_ERROR) + +project(note_server LANGUAGES CXX) + +# Require C++11 +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# Threads needed for on some systems, and for on Linux +set(THREADS_PREFER_PTHREAD_FLAG TRUE) +find_package(Threads REQUIRED) + +# Add executable +add_executable(note_server note_server.cc) + +# Include directory for httplib.h +target_include_directories(note_server PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + +# Link libraries +target_link_libraries(note_server PRIVATE + Threads::Threads + $<$:ws2_32> + $<$:crypt32> +) diff --git a/build_note_server/CMakeCache.txt b/build_note_server/CMakeCache.txt new file mode 100644 index 0000000000..2cdcba6d17 --- /dev/null +++ b/build_note_server/CMakeCache.txt @@ -0,0 +1,109 @@ +# This is the CMakeCache file. +# For build in directory: d:/DraftsJ/WinCppDraftsJ/WCD2J/SMJ/cpp-httplib/build_note_server +# It was generated by CMake: D:/ProgrammesJ/CmakeJ/bin/cmake.exe +# You can edit this file to change values found and used by cmake. +# If you do not want to change any of the values, simply exit the editor. +# If you do want to change a value, simply edit, save, and exit the editor. +# The syntax for the file is as follows: +# KEY:TYPE=VALUE +# KEY is the name of a variable in the cache. +# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!. +# VALUE is the current value for the KEY. + +######################## +# EXTERNAL cache entries +######################## + +//No help, variable specified on the command line. +CMAKE_BUILD_TYPE:UNINITIALIZED=Release + +//Value Computed by CMake. +CMAKE_FIND_PACKAGE_REDIRECTS_DIR:STATIC=D:/DraftsJ/WinCppDraftsJ/WCD2J/SMJ/cpp-httplib/build_note_server/CMakeFiles/pkgRedirects + +//Path to a program. +CMAKE_MAKE_PROGRAM:FILEPATH=CMAKE_MAKE_PROGRAM-NOTFOUND + +//Value Computed by CMake +CMAKE_PROJECT_COMPAT_VERSION:STATIC= + +//Value Computed by CMake +CMAKE_PROJECT_DESCRIPTION:STATIC=A C++ header-only HTTP/HTTPS server and client library. + +//Value Computed by CMake +CMAKE_PROJECT_HOMEPAGE_URL:STATIC=https://github.com/yhirose/cpp-httplib + +//Value Computed by CMake +CMAKE_PROJECT_NAME:STATIC=httplib + +//Value Computed by CMake +CMAKE_PROJECT_SPDX_LICENSE:STATIC= + +//Value Computed by CMake +CMAKE_PROJECT_VERSION:STATIC=0.37.1 + +//Value Computed by CMake +CMAKE_PROJECT_VERSION_MAJOR:STATIC=0 + +//Value Computed by CMake +CMAKE_PROJECT_VERSION_MINOR:STATIC=37 + +//Value Computed by CMake +CMAKE_PROJECT_VERSION_PATCH:STATIC=1 + +//Value Computed by CMake +CMAKE_PROJECT_VERSION_TWEAK:STATIC= + +//Value Computed by CMake +httplib_BINARY_DIR:STATIC=D:/DraftsJ/WinCppDraftsJ/WCD2J/SMJ/cpp-httplib/build_note_server + +//Value Computed by CMake +httplib_IS_TOP_LEVEL:STATIC=ON + +//Value Computed by CMake +httplib_SOURCE_DIR:STATIC=D:/DraftsJ/WinCppDraftsJ/WCD2J/SMJ/cpp-httplib + + +######################## +# INTERNAL cache entries +######################## + +//This is the directory where this CMakeCache.txt was created +CMAKE_CACHEFILE_DIR:INTERNAL=d:/DraftsJ/WinCppDraftsJ/WCD2J/SMJ/cpp-httplib/build_note_server +//Major version of cmake used to create the current loaded cache +CMAKE_CACHE_MAJOR_VERSION:INTERNAL=4 +//Minor version of cmake used to create the current loaded cache +CMAKE_CACHE_MINOR_VERSION:INTERNAL=3 +//Patch version of cmake used to create the current loaded cache +CMAKE_CACHE_PATCH_VERSION:INTERNAL=0 +//Path to CMake executable. +CMAKE_COMMAND:INTERNAL=D:/ProgrammesJ/CmakeJ/bin/cmake.exe +//Path to cpack program executable. +CMAKE_CPACK_COMMAND:INTERNAL=D:/ProgrammesJ/CmakeJ/bin/cpack.exe +//Path to ctest program executable. +CMAKE_CTEST_COMMAND:INTERNAL=D:/ProgrammesJ/CmakeJ/bin/ctest.exe +//Path to cache edit program executable. +CMAKE_EDIT_COMMAND:INTERNAL=D:/ProgrammesJ/CmakeJ/bin/cmake-gui.exe +//Name of external makefile project generator. +CMAKE_EXTRA_GENERATOR:INTERNAL= +//Name of generator. +CMAKE_GENERATOR:INTERNAL=MinGW Makefiles +//Generator instance identifier. +CMAKE_GENERATOR_INSTANCE:INTERNAL= +//Name of generator platform. +CMAKE_GENERATOR_PLATFORM:INTERNAL= +//Name of generator toolset. +CMAKE_GENERATOR_TOOLSET:INTERNAL= +//Source directory with the top level CMakeLists.txt file for this +// project +CMAKE_HOME_DIRECTORY:INTERNAL=D:/DraftsJ/WinCppDraftsJ/WCD2J/SMJ/cpp-httplib +//Name of CMakeLists files to read +CMAKE_LIST_FILE_NAME:INTERNAL=CMakeLists.txt +//ADVANCED property for variable: CMAKE_MAKE_PROGRAM +CMAKE_MAKE_PROGRAM-ADVANCED:INTERNAL=1 +//number of local generators +CMAKE_NUMBER_OF_MAKEFILES:INTERNAL=1 +//Platform information initialized +CMAKE_PLATFORM_INFO_INITIALIZED:INTERNAL=1 +//Path to CMake installation. +CMAKE_ROOT:INTERNAL=D:/ProgrammesJ/CmakeJ/share/cmake-4.3 + diff --git a/build_note_server/CMakeFiles/4.3.0-rc2/CMakeSystem.cmake b/build_note_server/CMakeFiles/4.3.0-rc2/CMakeSystem.cmake new file mode 100644 index 0000000000..88ce3654c3 --- /dev/null +++ b/build_note_server/CMakeFiles/4.3.0-rc2/CMakeSystem.cmake @@ -0,0 +1,15 @@ +set(CMAKE_HOST_SYSTEM "Windows-10.0.26200") +set(CMAKE_HOST_SYSTEM_NAME "Windows") +set(CMAKE_HOST_SYSTEM_VERSION "10.0.26200") +set(CMAKE_HOST_SYSTEM_PROCESSOR "AMD64") + + + +set(CMAKE_SYSTEM "Windows-10.0.26200") +set(CMAKE_SYSTEM_NAME "Windows") +set(CMAKE_SYSTEM_VERSION "10.0.26200") +set(CMAKE_SYSTEM_PROCESSOR "AMD64") + +set(CMAKE_CROSSCOMPILING "FALSE") + +set(CMAKE_SYSTEM_LOADED 1) diff --git a/build_note_server/CMakeFiles/CMakeConfigureLog.yaml b/build_note_server/CMakeFiles/CMakeConfigureLog.yaml new file mode 100644 index 0000000000..c364ba1883 --- /dev/null +++ b/build_note_server/CMakeFiles/CMakeConfigureLog.yaml @@ -0,0 +1,143 @@ + +--- +events: + - + kind: "message-v1" + backtrace: + - "D:/ProgrammesJ/CmakeJ/share/cmake-4.3/Modules/CMakeDetermineSystem.cmake:212 (message)" + - "CMakeLists.txt:88 (project)" + message: | + The system is: Windows - 10.0.26200 - AMD64 + - + kind: "find-v1" + backtrace: + - "D:/ProgrammesJ/CmakeJ/share/cmake-4.3/Modules/CMakeMinGWFindMake.cmake:5 (find_program)" + - "CMakeLists.txt:88 (project)" + mode: "program" + variable: "CMAKE_MAKE_PROGRAM" + description: "Path to a program." + settings: + SearchFramework: "NEVER" + SearchAppBundle: "NEVER" + CMAKE_FIND_USE_CMAKE_PATH: true + CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_CMAKE_SYSTEM_PATH: true + CMAKE_FIND_USE_INSTALL_PREFIX: true + names: + - "mingw32-make.exe" + candidate_directories: + - "C:/Users/jizhenyi/.trae-cn/tools/trae-gopls/current/" + - "C:/Users/jizhenyi/.trae-cn/sdks/workspaces/4ec56500/versions/node/current/" + - "C:/Users/jizhenyi/.trae-cn/sdks/versions/node/current/" + - "D:/ProgrammesJ/Python313J/Scripts/" + - "D:/ProgrammesJ/Python313J/" + - "D:/ProgrammesJ/VMwareWorkstationJ/bin/" + - "C:/Windows/System32/" + - "C:/Windows/" + - "C:/Windows/System32/wbem/" + - "C:/Windows/System32/WindowsPowerShell/v1.0/" + - "C:/Windows/System32/OpenSSH/" + - "C:/Program Files/dotnet/" + - "D:/SoftWaresJ/BandizipJ/" + - "C:/Program Files/Microsoft SQL Server/170/Tools/Binn/" + - "C:/Program Files/Microsoft SQL Server/Client SDK/ODBC/170/Tools/Binn/" + - "D:/Windows Kits/10/Windows Performance Toolkit/" + - "D:/ProgrammesJ/OpenVPNJ/OpenVPN/bin/" + - "D:/ProgrammesJ/GitJ/Git/cmd/" + - "D:/ProgrammesJ/CmakeJ/bin/" + - "C:/Users/jizhenyi/AppData/Local/Microsoft/WindowsApps/" + - "D:/ProgrammesJ/MicrosoftVSCodeJ/Microsoft VS Code/bin/" + - "C:/Users/jizhenyi/.dotnet/tools/" + - "D:/ProgrammesJ/PyCharmJ/PyCharm 2025.1/bin/" + - "D:/ProgrammesJ/CursorJ/cursor/resources/app/bin/" + - "/REGISTRY-NOTFOUND/bin/" + - "c:/MinGW/bin/" + - "/MinGW/bin/" + - "/REGISTRY-NOTFOUND/MinGW/bin/" + searched_directories: + - "C:/Users/jizhenyi/.trae-cn/tools/trae-gopls/current/mingw32-make.exe.com" + - "C:/Users/jizhenyi/.trae-cn/tools/trae-gopls/current/mingw32-make.exe" + - "C:/Users/jizhenyi/.trae-cn/sdks/workspaces/4ec56500/versions/node/current/mingw32-make.exe.com" + - "C:/Users/jizhenyi/.trae-cn/sdks/workspaces/4ec56500/versions/node/current/mingw32-make.exe" + - "C:/Users/jizhenyi/.trae-cn/sdks/versions/node/current/mingw32-make.exe.com" + - "C:/Users/jizhenyi/.trae-cn/sdks/versions/node/current/mingw32-make.exe" + - "D:/ProgrammesJ/Python313J/Scripts/mingw32-make.exe.com" + - "D:/ProgrammesJ/Python313J/Scripts/mingw32-make.exe" + - "D:/ProgrammesJ/Python313J/mingw32-make.exe.com" + - "D:/ProgrammesJ/Python313J/mingw32-make.exe" + - "D:/ProgrammesJ/VMwareWorkstationJ/bin/mingw32-make.exe.com" + - "D:/ProgrammesJ/VMwareWorkstationJ/bin/mingw32-make.exe" + - "C:/Windows/System32/mingw32-make.exe.com" + - "C:/Windows/System32/mingw32-make.exe" + - "C:/Windows/mingw32-make.exe.com" + - "C:/Windows/mingw32-make.exe" + - "C:/Windows/System32/wbem/mingw32-make.exe.com" + - "C:/Windows/System32/wbem/mingw32-make.exe" + - "C:/Windows/System32/WindowsPowerShell/v1.0/mingw32-make.exe.com" + - "C:/Windows/System32/WindowsPowerShell/v1.0/mingw32-make.exe" + - "C:/Windows/System32/OpenSSH/mingw32-make.exe.com" + - "C:/Windows/System32/OpenSSH/mingw32-make.exe" + - "C:/Program Files/dotnet/mingw32-make.exe.com" + - "C:/Program Files/dotnet/mingw32-make.exe" + - "D:/SoftWaresJ/BandizipJ/mingw32-make.exe.com" + - "D:/SoftWaresJ/BandizipJ/mingw32-make.exe" + - "C:/Program Files/Microsoft SQL Server/170/Tools/Binn/mingw32-make.exe.com" + - "C:/Program Files/Microsoft SQL Server/170/Tools/Binn/mingw32-make.exe" + - "C:/Program Files/Microsoft SQL Server/Client SDK/ODBC/170/Tools/Binn/mingw32-make.exe.com" + - "C:/Program Files/Microsoft SQL Server/Client SDK/ODBC/170/Tools/Binn/mingw32-make.exe" + - "D:/Windows Kits/10/Windows Performance Toolkit/mingw32-make.exe.com" + - "D:/Windows Kits/10/Windows Performance Toolkit/mingw32-make.exe" + - "D:/ProgrammesJ/OpenVPNJ/OpenVPN/bin/mingw32-make.exe.com" + - "D:/ProgrammesJ/OpenVPNJ/OpenVPN/bin/mingw32-make.exe" + - "D:/ProgrammesJ/GitJ/Git/cmd/mingw32-make.exe.com" + - "D:/ProgrammesJ/GitJ/Git/cmd/mingw32-make.exe" + - "D:/ProgrammesJ/CmakeJ/bin/mingw32-make.exe.com" + - "D:/ProgrammesJ/CmakeJ/bin/mingw32-make.exe" + - "C:/Users/jizhenyi/AppData/Local/Microsoft/WindowsApps/mingw32-make.exe.com" + - "C:/Users/jizhenyi/AppData/Local/Microsoft/WindowsApps/mingw32-make.exe" + - "D:/ProgrammesJ/MicrosoftVSCodeJ/Microsoft VS Code/bin/mingw32-make.exe.com" + - "D:/ProgrammesJ/MicrosoftVSCodeJ/Microsoft VS Code/bin/mingw32-make.exe" + - "C:/Users/jizhenyi/.dotnet/tools/mingw32-make.exe.com" + - "C:/Users/jizhenyi/.dotnet/tools/mingw32-make.exe" + - "D:/ProgrammesJ/PyCharmJ/PyCharm 2025.1/bin/mingw32-make.exe.com" + - "D:/ProgrammesJ/PyCharmJ/PyCharm 2025.1/bin/mingw32-make.exe" + - "D:/ProgrammesJ/CursorJ/cursor/resources/app/bin/mingw32-make.exe.com" + - "D:/ProgrammesJ/CursorJ/cursor/resources/app/bin/mingw32-make.exe" + - "/REGISTRY-NOTFOUND/bin/mingw32-make.exe.com" + - "/REGISTRY-NOTFOUND/bin/mingw32-make.exe" + - "c:/MinGW/bin/mingw32-make.exe.com" + - "c:/MinGW/bin/mingw32-make.exe" + - "/MinGW/bin/mingw32-make.exe.com" + - "/MinGW/bin/mingw32-make.exe" + - "/REGISTRY-NOTFOUND/MinGW/bin/mingw32-make.exe.com" + - "/REGISTRY-NOTFOUND/MinGW/bin/mingw32-make.exe" + found: false + search_context: + ENV{PATH}: + - "c:\\Users\\jizhenyi\\.trae-cn\\tools\\trae-gopls\\current" + - "c:\\Users\\jizhenyi\\.trae-cn\\sdks\\workspaces\\4ec56500\\versions\\node\\current" + - "c:\\Users\\jizhenyi\\.trae-cn\\sdks\\versions\\node\\current" + - "D:\\ProgrammesJ\\Python313J\\Scripts\\" + - "D:\\ProgrammesJ\\Python313J\\" + - "D:\\ProgrammesJ\\VMwareWorkstationJ\\bin\\" + - "C:\\WINDOWS\\system32" + - "C:\\WINDOWS" + - "C:\\WINDOWS\\System32\\Wbem" + - "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\" + - "C:\\WINDOWS\\System32\\OpenSSH\\" + - "C:\\Windows\\system32" + - "C:\\Program Files\\dotnet\\" + - "D:\\SoftWaresJ\\BandizipJ\\" + - "C:\\Program Files\\Microsoft SQL Server\\170\\Tools\\Binn\\" + - "C:\\Program Files\\Microsoft SQL Server\\Client SDK\\ODBC\\170\\Tools\\Binn\\" + - "D:\\Windows Kits\\10\\Windows Performance Toolkit\\" + - "D:\\ProgrammesJ\\OpenVPNJ\\OpenVPN\\bin" + - "D:\\ProgrammesJ\\GitJ\\Git\\cmd" + - "D:\\ProgrammesJ\\CmakeJ\\bin" + - "C:\\Users\\jizhenyi\\AppData\\Local\\Microsoft\\WindowsApps" + - "D:\\ProgrammesJ\\MicrosoftVSCodeJ\\Microsoft VS Code\\bin" + - "C:\\Users\\jizhenyi\\.dotnet\\tools" + - "D:\\ProgrammesJ\\PyCharmJ\\PyCharm 2025.1\\bin" + - "D:\\ProgrammesJ\\CursorJ\\cursor\\resources\\app\\bin" +... diff --git a/build_note_server/CMakeFiles/cmake.check_cache b/build_note_server/CMakeFiles/cmake.check_cache new file mode 100644 index 0000000000..3dccd73172 --- /dev/null +++ b/build_note_server/CMakeFiles/cmake.check_cache @@ -0,0 +1 @@ +# This file is generated by cmake for dependency checking of the CMakeCache.txt file diff --git a/note_server.cc b/note_server.cc new file mode 100644 index 0000000000..aa0f0a634c --- /dev/null +++ b/note_server.cc @@ -0,0 +1,193 @@ +#include +#include +#include +#include +#include +#include + +using namespace httplib; + +struct Note { + uint64_t id; + std::string title; + std::string content; +}; + +std::map notes; +uint64_t next_id = 1; + +std::string escape_json(const std::string &s) { + std::string result; + for (char c : s) { + switch (c) { + case '"': result += "\\\""; break; + case '\\': result += "\\\\"; break; + case '\b': result += "\\b"; break; + case '\f': result += "\\f"; break; + case '\n': result += "\\n"; break; + case '\r': result += "\\r"; break; + case '\t': result += "\\t"; break; + default: result += c; break; + } + } + return result; +} + +std::string note_to_json(const Note ¬e) { + return "{\"id\":" + std::to_string(note.id) + + ",\"title\":\"" + escape_json(note.title) + + "\",\"content\":\"" + escape_json(note.content) + "\"}"; +} + +std::string notes_to_json(const std::map ¬es) { + std::string result = "["; + bool first = true; + for (const auto &pair : notes) { + if (!first) { + result += ","; + } + result += note_to_json(pair.second); + first = false; + } + result += "]"; + return result; +} + +std::string trim(const std::string &s) { + size_t start = s.find_first_not_of(" \t\n\r"); + size_t end = s.find_last_not_of(" \t\n\r"); + if (start == std::string::npos) return ""; + return s.substr(start, end - start + 1); +} + +bool parse_json_value(const std::string &json, const std::string &key, std::string &value) { + std::string search_key = "\"" + key + "\":"; + size_t pos = json.find(search_key); + if (pos == std::string::npos) return false; + + pos += search_key.length(); + pos = json.find_first_not_of(" \t\n\r", pos); + if (pos == std::string::npos) return false; + + if (json[pos] == '"') { + pos++; + size_t end_pos = pos; + while (end_pos < json.length()) { + if (json[end_pos] == '"' && (end_pos == 0 || json[end_pos - 1] != '\\')) { + break; + } + end_pos++; + } + if (end_pos < json.length()) { + value = json.substr(pos, end_pos - pos); + return true; + } + } + return false; +} + +int main(void) { + Server svr; + + if (!svr.is_valid()) { + std::cerr << "server has an error..." << std::endl; + return -1; + } + + svr.Post("/notes", [](const Request &req, Response &res) { + std::string title, content; + if (!parse_json_value(req.body, "title", title) || + !parse_json_value(req.body, "content", content)) { + res.status = 400; + res.set_content("{\"error\":\"Missing required fields: title and content are required\"}", "application/json"); + return; + } + + title = trim(title); + content = trim(content); + + if (title.empty()) { + res.status = 400; + res.set_content("{\"error\":\"Title cannot be empty\"}", "application/json"); + return; + } + + Note note; + note.id = next_id++; + note.title = title; + note.content = content; + notes[note.id] = note; + + res.status = 201; + res.set_content(note_to_json(note), "application/json"); + }); + + svr.Get("/notes", [](const Request & /*req*/, Response &res) { + res.set_content(notes_to_json(notes), "application/json"); + }); + + svr.Put(R"(/notes/(\d+))", [](const Request &req, Response &res) { + uint64_t id = std::stoull(req.matches[1]); + + auto it = notes.find(id); + if (it == notes.end()) { + res.status = 404; + res.set_content("{\"error\":\"Note not found\"}", "application/json"); + return; + } + + std::string title, content; + if (!parse_json_value(req.body, "title", title) || + !parse_json_value(req.body, "content", content)) { + res.status = 400; + res.set_content("{\"error\":\"Missing required fields: title and content are required\"}", "application/json"); + return; + } + + title = trim(title); + content = trim(content); + + if (title.empty()) { + res.status = 400; + res.set_content("{\"error\":\"Title cannot be empty\"}", "application/json"); + return; + } + + it->second.title = title; + it->second.content = content; + + res.set_content(note_to_json(it->second), "application/json"); + }); + + svr.Delete(R"(/notes/(\d+))", [](const Request &req, Response &res) { + uint64_t id = std::stoull(req.matches[1]); + + auto it = notes.find(id); + if (it == notes.end()) { + res.status = 404; + res.set_content("{\"error\":\"Note not found\"}", "application/json"); + return; + } + + notes.erase(it); + res.status = 204; + }); + + svr.set_error_handler([](const Request & /*req*/, Response &res) { + res.set_content("{\"error\":\"Not found\"}", "application/json"); + }); + + std::cout << "Note server starting on http://localhost:8080" << std::endl; + std::cout << "Available endpoints:" << std::endl; + std::cout << " POST /notes - Create a new note" << std::endl; + std::cout << " GET /notes - Get all notes" << std::endl; + std::cout << " PUT /notes/{id} - Update a note" << std::endl; + std::cout << " DELETE /notes/{id} - Delete a note" << std::endl; + std::cout << std::endl; + std::cout << "Example request body:" << std::endl; + std::cout << " {\"title\":\"Note Title\",\"content\":\"Note Content\"}" << std::endl; + + svr.listen("localhost", 8080); + + return 0; +} diff --git a/note_server.py b/note_server.py new file mode 100644 index 0000000000..5e74e55089 --- /dev/null +++ b/note_server.py @@ -0,0 +1,157 @@ +from http.server import HTTPServer, BaseHTTPRequestHandler +import json +from urllib.parse import urlparse + +notes = {} +next_id = 1 + +class NoteHandler(BaseHTTPRequestHandler): + def _send_response(self, status_code, data=None): + self.send_response(status_code) + self.send_header('Content-Type', 'application/json') + self.send_header('Access-Control-Allow-Origin', '*') + self.send_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') + self.send_header('Access-Control-Allow-Headers', 'Content-Type') + self.end_headers() + if data is not None: + self.wfile.write(json.dumps(data).encode('utf-8')) + + def do_OPTIONS(self): + self.send_response(200) + self.send_header('Access-Control-Allow-Origin', '*') + self.send_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') + self.send_header('Access-Control-Allow-Headers', 'Content-Type') + self.end_headers() + + def do_GET(self): + parsed_path = urlparse(self.path) + if parsed_path.path == '/notes': + notes_list = list(notes.values()) + self._send_response(200, notes_list) + else: + self._send_response(404, {'error': 'Not found'}) + + def do_POST(self): + parsed_path = urlparse(self.path) + if parsed_path.path == '/notes': + content_length = int(self.headers.get('Content-Length', 0)) + if content_length == 0: + self._send_response(400, {'error': 'Missing request body'}) + return + + body = self.rfile.read(content_length).decode('utf-8') + try: + data = json.loads(body) + except json.JSONDecodeError: + self._send_response(400, {'error': 'Invalid JSON'}) + return + + if 'title' not in data or 'content' not in data: + self._send_response(400, {'error': 'Missing required fields: title and content are required'}) + return + + title = data['title'].strip() + content = data['content'].strip() + + if not title: + self._send_response(400, {'error': 'Title cannot be empty'}) + return + + global next_id + note = { + 'id': next_id, + 'title': title, + 'content': content + } + notes[next_id] = note + next_id += 1 + + self._send_response(201, note) + else: + self._send_response(404, {'error': 'Not found'}) + + def do_PUT(self): + parsed_path = urlparse(self.path) + if parsed_path.path.startswith('/notes/'): + try: + note_id = int(parsed_path.path.split('/')[2]) + except (IndexError, ValueError): + self._send_response(404, {'error': 'Note not found'}) + return + + if note_id not in notes: + self._send_response(404, {'error': 'Note not found'}) + return + + content_length = int(self.headers.get('Content-Length', 0)) + if content_length == 0: + self._send_response(400, {'error': 'Missing request body'}) + return + + body = self.rfile.read(content_length).decode('utf-8') + try: + data = json.loads(body) + except json.JSONDecodeError: + self._send_response(400, {'error': 'Invalid JSON'}) + return + + if 'title' not in data or 'content' not in data: + self._send_response(400, {'error': 'Missing required fields: title and content are required'}) + return + + title = data['title'].strip() + content = data['content'].strip() + + if not title: + self._send_response(400, {'error': 'Title cannot be empty'}) + return + + note = notes[note_id] + note['title'] = title + note['content'] = content + + self._send_response(200, note) + else: + self._send_response(404, {'error': 'Not found'}) + + def do_DELETE(self): + parsed_path = urlparse(self.path) + if parsed_path.path.startswith('/notes/'): + try: + note_id = int(parsed_path.path.split('/')[2]) + except (IndexError, ValueError): + self._send_response(404, {'error': 'Note not found'}) + return + + if note_id not in notes: + self._send_response(404, {'error': 'Note not found'}) + return + + del notes[note_id] + self._send_response(204, None) + else: + self._send_response(404, {'error': 'Not found'}) + +def main(): + port = 8080 + server_address = ('localhost', port) + httpd = HTTPServer(server_address, NoteHandler) + + print(f"Note server starting on http://localhost:{port}") + print("Available endpoints:") + print(" POST /notes - Create a new note") + print(" GET /notes - Get all notes") + print(" PUT /notes/{id} - Update a note") + print(" DELETE /notes/{id} - Delete a note") + print() + print("Example request body:") + print(' {"title":"Note Title","content":"Note Content"}') + + try: + httpd.serve_forever() + except KeyboardInterrupt: + print("\nServer stopped.") + httpd.server_close() + +if __name__ == '__main__': + main() diff --git a/test_api.py b/test_api.py new file mode 100644 index 0000000000..752a2e4249 --- /dev/null +++ b/test_api.py @@ -0,0 +1,79 @@ +import requests +import json + +BASE_URL = "http://localhost:8080" + +def test_create_note(): + print("=== Testing POST /notes ===") + data = { + "title": "Test Note", + "content": "This is a test note content" + } + response = requests.post(f"{BASE_URL}/notes", json=data) + print(f"Status Code: {response.status_code}") + print(f"Response: {response.text}") + assert response.status_code == 201, f"Expected 201, got {response.status_code}" + return response.json()["id"] + +def test_get_notes(): + print("\n=== Testing GET /notes ===") + response = requests.get(f"{BASE_URL}/notes") + print(f"Status Code: {response.status_code}") + print(f"Response: {response.text}") + assert response.status_code == 200, f"Expected 200, got {response.status_code}" + return response.json() + +def test_update_note(note_id): + print("\n=== Testing PUT /notes/{id} ===") + data = { + "title": "Updated Test Note", + "content": "This is updated content" + } + response = requests.put(f"{BASE_URL}/notes/{note_id}", json=data) + print(f"Status Code: {response.status_code}") + print(f"Response: {response.text}") + assert response.status_code == 200, f"Expected 200, got {response.status_code}" + return response.json() + +def test_delete_note(note_id): + print("\n=== Testing DELETE /notes/{id} ===") + response = requests.delete(f"{BASE_URL}/notes/{note_id}") + print(f"Status Code: {response.status_code}") + assert response.status_code == 204, f"Expected 204, got {response.status_code}" + +def test_invalid_note(): + print("\n=== Testing invalid note ID ===") + response = requests.get(f"{BASE_URL}/notes/99999") + print(f"Status Code: {response.status_code}") + print(f"Response: {response.text}") + assert response.status_code == 404, f"Expected 404, got {response.status_code}" + +def test_missing_fields(): + print("\n=== Testing missing fields ===") + data = { + "title": "" + } + response = requests.post(f"{BASE_URL}/notes", json=data) + print(f"Status Code: {response.status_code}") + print(f"Response: {response.text}") + assert response.status_code == 400, f"Expected 400, got {response.status_code}" + +if __name__ == "__main__": + try: + # Test full workflow + note_id = test_create_note() + test_get_notes() + test_update_note(note_id) + test_get_notes() + test_delete_note(note_id) + test_get_notes() + + # Test error cases + test_invalid_note() + test_missing_fields() + + print("\n=== All tests passed! ===") + except AssertionError as e: + print(f"\n=== Test failed: {e} ===") + except Exception as e: + print(f"\n=== Error: {e} ===") diff --git a/test_api_simple.py b/test_api_simple.py new file mode 100644 index 0000000000..923cb11a6f --- /dev/null +++ b/test_api_simple.py @@ -0,0 +1,80 @@ +import http.client +import json + +def test_api(): + conn = http.client.HTTPConnection('localhost', 8080) + + # Test POST /notes + print("=== Testing POST /notes ===") + data = json.dumps({"title": "Test Note", "content": "This is a test note"}) + headers = {'Content-Type': 'application/json'} + conn.request('POST', '/notes', body=data, headers=headers) + response = conn.getresponse() + print(f"Status: {response.status}") + response_data = response.read().decode() + print(f"Response: {response_data}") + + if response.status == 201: + note = json.loads(response_data) + note_id = note['id'] + print(f"Created note with ID: {note_id}") + else: + print("POST request failed") + conn.close() + return + + # Test GET /notes + print("\n=== Testing GET /notes ===") + conn.request('GET', '/notes') + response = conn.getresponse() + print(f"Status: {response.status}") + print(f"Response: {response.read().decode()}") + + # Test PUT /notes/{id} + print("\n=== Testing PUT /notes/{id} ===") + data = json.dumps({"title": "Updated Note", "content": "Updated content"}) + conn.request('PUT', f'/notes/{note_id}', body=data, headers=headers) + response = conn.getresponse() + print(f"Status: {response.status}") + print(f"Response: {response.read().decode()}") + + # Test GET /notes after update + print("\n=== Testing GET /notes after update ===") + conn.request('GET', '/notes') + response = conn.getresponse() + print(f"Status: {response.status}") + print(f"Response: {response.read().decode()}") + + # Test DELETE /notes/{id} + print("\n=== Testing DELETE /notes/{id} ===") + conn.request('DELETE', f'/notes/{note_id}') + response = conn.getresponse() + print(f"Status: {response.status}") + + # Test GET /notes after delete + print("\n=== Testing GET /notes after delete ===") + conn.request('GET', '/notes') + response = conn.getresponse() + print(f"Status: {response.status}") + print(f"Response: {response.read().decode()}") + + # Test invalid ID + print("\n=== Testing invalid note ID ===") + conn.request('GET', '/notes/99999') + response = conn.getresponse() + print(f"Status: {response.status}") + print(f"Response: {response.read().decode()}") + + # Test missing fields + print("\n=== Testing missing fields ===") + data = json.dumps({"title": ""}) + conn.request('POST', '/notes', body=data, headers=headers) + response = conn.getresponse() + print(f"Status: {response.status}") + print(f"Response: {response.read().decode()}") + + conn.close() + print("\n=== All tests completed! ===") + +if __name__ == "__main__": + test_api() diff --git a/test_combined.py b/test_combined.py new file mode 100644 index 0000000000..86057523ca --- /dev/null +++ b/test_combined.py @@ -0,0 +1,107 @@ +import threading +import time +import http.client +import json +from http.server import HTTPServer +from note_server import NoteHandler + +# Start server in a separate thread +server = None +server_thread = None + +def run_server(): + global server + server_address = ('localhost', 8080) + server = HTTPServer(server_address, NoteHandler) + server.serve_forever() + +def run_tests(): + time.sleep(1) # Give server time to start + + conn = http.client.HTTPConnection('localhost', 8080) + + # Test POST /notes + print("=== Testing POST /notes ===") + data = json.dumps({"title": "Test Note", "content": "This is a test note"}) + headers = {'Content-Type': 'application/json'} + conn.request('POST', '/notes', body=data, headers=headers) + response = conn.getresponse() + print(f"Status: {response.status}") + response_data = response.read().decode() + print(f"Response: {response_data}") + + if response.status == 201: + note = json.loads(response_data) + note_id = note['id'] + print(f"Created note with ID: {note_id}") + else: + print("POST request failed") + conn.close() + return + + # Test GET /notes + print("\n=== Testing GET /notes ===") + conn.request('GET', '/notes') + response = conn.getresponse() + print(f"Status: {response.status}") + print(f"Response: {response.read().decode()}") + + # Test PUT /notes/{id} + print("\n=== Testing PUT /notes/{id} ===") + data = json.dumps({"title": "Updated Note", "content": "Updated content"}) + conn.request('PUT', f'/notes/{note_id}', body=data, headers=headers) + response = conn.getresponse() + print(f"Status: {response.status}") + print(f"Response: {response.read().decode()}") + + # Test GET /notes after update + print("\n=== Testing GET /notes after update ===") + conn.request('GET', '/notes') + response = conn.getresponse() + print(f"Status: {response.status}") + print(f"Response: {response.read().decode()}") + + # Test DELETE /notes/{id} + print("\n=== Testing DELETE /notes/{id} ===") + conn.request('DELETE', f'/notes/{note_id}') + response = conn.getresponse() + print(f"Status: {response.status}") + + # Test GET /notes after delete + print("\n=== Testing GET /notes after delete ===") + conn.request('GET', '/notes') + response = conn.getresponse() + print(f"Status: {response.status}") + print(f"Response: {response.read().decode()}") + + # Test invalid ID + print("\n=== Testing invalid note ID ===") + conn.request('GET', '/notes/99999') + response = conn.getresponse() + print(f"Status: {response.status}") + print(f"Response: {response.read().decode()}") + + # Test missing fields + print("\n=== Testing missing fields ===") + data = json.dumps({"title": ""}) + conn.request('POST', '/notes', body=data, headers=headers) + response = conn.getresponse() + print(f"Status: {response.status}") + print(f"Response: {response.read().decode()}") + + conn.close() + print("\n=== All tests completed! ===") + +if __name__ == "__main__": + # Start server thread + server_thread = threading.Thread(target=run_server) + server_thread.daemon = True + server_thread.start() + + # Run tests + run_tests() + + # Cleanup + if server: + server.shutdown() + server.server_close() diff --git a/test_subprocess.py b/test_subprocess.py new file mode 100644 index 0000000000..33e1aa866d --- /dev/null +++ b/test_subprocess.py @@ -0,0 +1,101 @@ +import subprocess +import time +import http.client +import json +import sys + +def run_test(): + # Start the server + server_process = subprocess.Popen( + [sys.executable, 'note_server.py'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + # Wait for server to start + time.sleep(2) + + try: + conn = http.client.HTTPConnection('localhost', 8080, timeout=5) + + # Test POST /notes + print("=== Testing POST /notes ===") + data = json.dumps({"title": "Test Note", "content": "This is a test note"}) + headers = {'Content-Type': 'application/json'} + conn.request('POST', '/notes', body=data, headers=headers) + response = conn.getresponse() + print(f"Status: {response.status}") + response_data = response.read().decode() + print(f"Response: {response_data}") + + if response.status == 201: + note = json.loads(response_data) + note_id = note['id'] + print(f"Created note with ID: {note_id}") + else: + print("POST request failed") + return + + # Test GET /notes + print("\n=== Testing GET /notes ===") + conn.request('GET', '/notes') + response = conn.getresponse() + print(f"Status: {response.status}") + print(f"Response: {response.read().decode()}") + + # Test PUT /notes/{id} + print("\n=== Testing PUT /notes/{id} ===") + data = json.dumps({"title": "Updated Note", "content": "Updated content"}) + conn.request('PUT', f'/notes/{note_id}', body=data, headers=headers) + response = conn.getresponse() + print(f"Status: {response.status}") + print(f"Response: {response.read().decode()}") + + # Test GET /notes after update + print("\n=== Testing GET /notes after update ===") + conn.request('GET', '/notes') + response = conn.getresponse() + print(f"Status: {response.status}") + print(f"Response: {response.read().decode()}") + + # Test DELETE /notes/{id} + print("\n=== Testing DELETE /notes/{id} ===") + conn.request('DELETE', f'/notes/{note_id}') + response = conn.getresponse() + print(f"Status: {response.status}") + + # Test GET /notes after delete + print("\n=== Testing GET /notes after delete ===") + conn.request('GET', '/notes') + response = conn.getresponse() + print(f"Status: {response.status}") + print(f"Response: {response.read().decode()}") + + # Test invalid ID + print("\n=== Testing invalid note ID ===") + conn.request('GET', '/notes/99999') + response = conn.getresponse() + print(f"Status: {response.status}") + print(f"Response: {response.read().decode()}") + + # Test missing fields + print("\n=== Testing missing fields ===") + data = json.dumps({"title": ""}) + conn.request('POST', '/notes', body=data, headers=headers) + response = conn.getresponse() + print(f"Status: {response.status}") + print(f"Response: {response.read().decode()}") + + conn.close() + print("\n=== All tests completed successfully! ===") + + except Exception as e: + print(f"Error: {e}") + finally: + # Stop the server + server_process.terminate() + server_process.wait() + +if __name__ == "__main__": + run_test()