diff --git a/docs/news.rst b/docs/news.rst index 37e27325..fff2dd9f 100644 --- a/docs/news.rst +++ b/docs/news.rst @@ -11,6 +11,7 @@ Unreleased Added ~~~~~ +- Respond to HTTP ``OPTIONS`` method requests. - Add "Last modified" column to the directory listings of log files and item feeds. - Add environment variables to override common options. See :doc:`config`. - Add documentation on how to add webservices (endpoints). See :ref:`config-services`. diff --git a/integration_tests/test_webservice.py b/integration_tests/test_webservice.py index 1fcb7d33..101b263e 100644 --- a/integration_tests/test_webservice.py +++ b/integration_tests/test_webservice.py @@ -1,3 +1,5 @@ +import pytest +import requests import scrapy from integration_tests import req @@ -11,6 +13,28 @@ def assert_webservice(method, path, expected, **kwargs): assert json == expected +@pytest.mark.parametrize( + "webservice,method", + [ + ("daemonstatus", "GET"), + ("addversion", "POST"), + ("schedule", "POST"), + ("cancel", "POST"), + ("listprojects", "GET"), + ("listversions", "GET"), + ("listspiders", "GET"), + ("listjobs", "GET"), + ("delversion", "POST"), + ("delproject", "POST"), + ], +) +def test_options(webservice, method): + response = requests.options(f"http://127.0.0.1:6800/{webservice}.json", auth=("hello12345", "67890world")) + assert response.status_code == 204, response.status_code + assert response.content == b'' + assert response.headers['Allow'] == f"OPTIONS, HEAD, {method}" + + def test_daemonstatus(): assert_webservice( "get", diff --git a/scrapyd/webservice.py b/scrapyd/webservice.py index 86348116..72b62782 100644 --- a/scrapyd/webservice.py +++ b/scrapyd/webservice.py @@ -5,6 +5,7 @@ from io import BytesIO from twisted.python import log +from twisted.web import http from scrapyd.jobstorage import job_items_url, job_log_url from scrapyd.utils import JsonResource, UtilsCache, get_spider_list, native_stringify_dict @@ -26,6 +27,16 @@ def render(self, txrequest): r = {"node_name": self.root.nodename, "status": "error", "message": str(e)} return self.render_object(r, txrequest).encode('utf-8') + def render_OPTIONS(self, txrequest): + methods = ['OPTIONS', 'HEAD'] + if hasattr(self, 'render_GET'): + methods.append('GET') + if hasattr(self, 'render_POST'): + methods.append('POST') + txrequest.setHeader('Allow', ', '.join(methods)) + txrequest.setResponseCode(http.NO_CONTENT) + return b'' + class DaemonStatus(WsResource):