From 27568ac1c9571362cee4aecdc6d140c08ac5c50f Mon Sep 17 00:00:00 2001 From: Vasilis Gkoles Date: Mon, 15 Jul 2024 16:27:44 +0300 Subject: [PATCH 1/3] Fix OGC services throwing bad request --- src/linkchecker.py | 146 ++++++++++++++++++++++++++++++--------------- 1 file changed, 97 insertions(+), 49 deletions(-) diff --git a/src/linkchecker.py b/src/linkchecker.py index de82448..77e0355 100644 --- a/src/linkchecker.py +++ b/src/linkchecker.py @@ -1,5 +1,6 @@ from bs4 import BeautifulSoup from dotenv import load_dotenv +from urllib.parse import urlparse, parse_qs, urlencode import subprocess import psycopg2 import psycopg2.extras @@ -119,24 +120,40 @@ def extract_links(url): print(f"Error extracting links from {url}: {e}") return [] -def run_linkchecker(urls): - for url in urls: - # Run LinkChecker Docker command with specified user and group IDs for each URL - process = subprocess.Popen([ - "linkchecker", - "--verbose", - "--check-extern", - "--recursion-level=1", - "--timeout=5", - "--output=csv", - url + "?f=html" - ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) +def check_single_url(url): + process = subprocess.Popen([ + "linkchecker", + "--verbose", + "--check-extern", + "--recursion-level=0", + "--timeout=5", + "--output=csv", + url + "?f=html" + ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + # Process.communicate is good for shorter-running processes + stdout, _ = process.communicate() + + return stdout.decode('utf-8').strip().split('\n') - # Process the output line by line and yield each line - for line in process.stdout: - yield line.decode('utf-8').strip() # Decode bytes to string and strip newline characters - # Wait for the process to finish - process.wait() +def run_linkchecker(url): + # Run LinkChecker Docker command with specified user and group IDs for each URL + process = subprocess.Popen([ + "linkchecker", + "--verbose", + "--check-extern", + "--recursion-level=1", + "--timeout=5", + "--output=csv", + url + "?f=html" + ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + # Process the output line by line and yield each line + # Memory efficient for large outputs + for line in process.stdout: + yield line.decode('utf-8').strip() # Decode bytes to string and strip newline characters + # Wait for the process to finish + process.wait() def insert_or_update_link(conn, urlname, status, result, info, warning, is_valid): @@ -223,6 +240,35 @@ def get_active_urls(conn): else: cur.execute("SELECT url FROM validation_history WHERE NOT deprecated") return [row[0] for row in cur.fetchall()] + +def determine_service_type(url): + ogc_patterns = ['/wms', '/wfs', '/csw', '/wcs', 'service='] + + if any(pattern in url.lower() for pattern in ogc_patterns): + parsed_url = urlparse(url) + query_params = parse_qs(parsed_url.query) + + query_params.pop('service', None) + query_params.pop('request', None) + + query_params['request'] = ['GetCapabilities'] + + if 'service' not in query_params: + if '/wms' in parsed_url.path.lower(): + query_params['service'] = ['WMS'] + elif '/wfs' in parsed_url.path.lower(): + query_params['service'] = ['WFS'] + elif '/csw' in parsed_url.path.lower(): + query_params['service'] = ['CSW'] + elif '/wcs' in parsed_url.path.lower(): + query_params['service'] = ['WCS'] + + new_query = urlencode(query_params, doseq=True) + new_url = parsed_url._replace(query=new_query).geturl() + + return new_url + + return url def main(): start_time = time.time() # Start timing @@ -247,43 +293,45 @@ def main(): extracted_links = extract_links(url) all_links.update(extracted_links) # Add new links to the set of all links - # Define the formats to be removed - formats_to_remove = [ - 'collections/' + collection + '/items?offset', - '?f=json' - ] - # Specify the fields to include in the CSV file fields_to_include = ['urlname', 'parentname', 'baseref', 'valid', 'result', 'warning', 'info'] print("Checking Links...") + # Run LinkChecker and process the output - for line in run_linkchecker(all_links): - if re.match(r'^http', line): - # Remove trailing semicolon and split by semicolon - values = line.rstrip(';').split(';') - - # Filter and pad values based on fields_to_include - filtered_values = [str(values[i]) if i < len(values) else "" for i in range(len(fields_to_include))] - - # Destructure filtered_values - urlname, parentname, baseref, valid, result, warning, info = filtered_values - # print(f""" - # Urlname: {urlname} - # parentname: {parentname} - # baseref: {baseref} - # valid: {valid} - # result: {result} - # warning: {warning} - # info: {info} - # Is valid: {is_valid_status(valid)} - # """) - is_valid = is_valid_status(valid) - - link_id = insert_or_update_link(conn, urlname, valid, result, info, warning, is_valid) - - # Insert parent information - insert_parent(conn, parentname, baseref, link_id) + urls_to_recheck = set() + print("Initial Link Checking...") + for url in all_links: + for line in run_linkchecker(url): + if re.match(r'^http', line): + values = line.rstrip(';').split(';') + urlname = values[0] + + # Parse initial check results + filtered_values = [str(values[i]) if i < len(values) else "" for i in range(len(fields_to_include))] + urlname, parentname, baseref, valid, result, warning, info = filtered_values + + # Determine if URL needs to be rechecked + processed_url = determine_service_type(urlname) + if processed_url != urlname: + urls_to_recheck.add(processed_url) + else: + # If URL doesn't need reprocessing, insert results directly + is_valid = is_valid_status(valid) + link_id = insert_or_update_link(conn, urlname, valid, result, info, warning, is_valid) + insert_parent(conn, parentname, baseref, link_id) + + print("Rechecking OGC processed URLs...") + for url in urls_to_recheck: + results = check_single_url(url) + for line in results: + if re.match(r'^http', line): + values = line.rstrip(';').split(';') + filtered_values = [str(values[i]) if i < len(values) else "" for i in range(len(fields_to_include))] + urlname, parentname, baseref, valid, result, warning, info = filtered_values + is_valid = is_valid_status(valid) + link_id = insert_or_update_link(conn, urlname, valid, result, info, warning, is_valid) + insert_parent(conn, parentname, baseref, link_id) # conn.commit() print("LinkChecker output written to PostgreSQL database") From 66460d1d5a02a744113cbbdf0296534bcc87bc07 Mon Sep 17 00:00:00 2001 From: Vasilis Goles Date: Mon, 15 Jul 2024 16:35:31 +0300 Subject: [PATCH 2/3] Update Dockerfile --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9045a24..c64e6cd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ ENV POSTGRES_HOST=host.docker.internal ENV POSTGRES_PORT=5432 ENV POSTGRES_DB=postgres ENV POSTGRES_USER=postgres -ENV POSTGRES_PASSWORD=w4qu+0sj +ENV POSTGRES_PASSWORD=***** WORKDIR /home/link-liveliness-assessment @@ -39,4 +39,4 @@ EXPOSE 8000 USER linky -# ENTRYPOINT [ "python3", "-m", "uvicorn", "api:app", "--reload", "--host", "0.0.0.0", "--port", "8000" ] \ No newline at end of file +# ENTRYPOINT [ "python3", "-m", "uvicorn", "api:app", "--reload", "--host", "0.0.0.0", "--port", "8000" ] From 4bfc5f6db3f4b8808eac69bf64229309a7200a64 Mon Sep 17 00:00:00 2001 From: Vasilis Gkoles Date: Tue, 20 Aug 2024 12:13:39 +0300 Subject: [PATCH 3/3] Prevent OGC services throwing bad request Fixes #11 --- README.md | 160 ++++++++++++++++++++------------------ images/categorization.png | Bin 0 -> 18578 bytes images/deprecated.png | Bin 0 -> 5560 bytes images/link_status.png | Bin 0 -> 4992 bytes images/timeouts.png | Bin 0 -> 4692 bytes images/val_history.png | Bin 0 -> 5586 bytes 6 files changed, 86 insertions(+), 74 deletions(-) create mode 100644 images/categorization.png create mode 100644 images/deprecated.png create mode 100644 images/link_status.png create mode 100644 images/timeouts.png create mode 100644 images/val_history.png diff --git a/README.md b/README.md index faf3951..677ce2d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ -# OGC API - Records; link liveliness assessment +# OGC API - Records; link liveliness assessment tool + +### Overview +The linkchecker component is designed to evaluate the validity and accuracy of links within metadata records in the OGC API - Records System. A component which evaluates for a set of metadata records (describing either data or knowledge sources), if: @@ -6,10 +9,9 @@ A component which evaluates for a set of metadata records (describing either dat - the links within the repository are valid - link metadata represents accurately the resource -The component either returns a http status: 200 (ok), 403 (non autorized), 404 (not found), 500 (error), ... -Status 302 is forwarded to new location and the test is repeated. +The component either returns a http status: 200 (ok), 401 (non autorized), 404 (not found), 500 (server error) -The component runs an evaluation for a single resource at request, or runs tests at intervals providing a history of availability +The component runs an evaluation for a single resource at request, or runs tests at intervals providing a history of availability A link either points to: @@ -21,13 +23,34 @@ If endpoint is API, some sanity checks can be performed on the API: - Identify if the API adopted any API-standard - IF an API standard is adopted, does the API support basic operations of that API + +The benefit of latter is that it provides more information then a simple ping to the index page of the API, typical examples of standardised API's are SOAP, +GraphQL, SPARQL, OpenAPI, WMS, WFS -The benefit of latter is that it provides more information then a simple ping to the index page of the API, typical examples of standardised API's are SOAP, GraphQL, SPARQL, OpenAPI, WMS, WFS - -## OGC API - records - -OGC is in the process of adopting the [OGC API - Records](https://github.com/opengeospatial/ogcapi-records) specification. A standardised API to interact with Catalogues. The specification includes a datamodel for metadata. This tool assesses the linkage section of any record in an OGC API - Records. - +***Sample response*** +``` + { + "id": 25, + "urlname": "https://demo.pycsw.org/gisdata/collections/metadata:main/queryables", + "parent_urls": [ + "https://demo.pycsw.org/gisdata/collections?f=html" + ], + "status": "200 OK", + "result": "", + "info": "True", + "warning": "", + "deprecated": null + } +``` +# OGC API - records +OGC is in the process of adopting the [OGC API - Records](https://github.com/opengeospatial/ogcapi-records) specification. +A standardised API to interact with Catalogues. The specification includes a datamodel for metadata. +This tool assesses the linkage section of any record in an OGC API - Records. + +OGC services (WMS, WFS, WCS, CSW) often return an HTTP 500 error or a 400 Bad Request when called without the necessary parameters. +This is because these services expect specific parameters to understand which operations to perform. +A handling for this URL formats has been done in order to detect and include the necessary parameters before being checked + Set the endpoint to be analysed as 2 environment variables ``` @@ -35,82 +58,71 @@ export OGCAPI_URL=https://soilwise-he.containers.wur.nl/cat/ export OGCAPI_COLLECTION=metadata:main ``` -## Source Code Brief Desrciption - -Running the linkchecker.py will utilize the requests library from python to get the relevant EJP Soil Catalogue source. -Run the command below -* python linkchecker.py -The urls selected from the requests will passed to linkchecker using the proper options. -The output generated will be written to a PostgreSQL database. -A .env is required to define the database connection parameters. -More specifically the following parameters must be specified - -``` - POSTGRES_HOST= - POSTGRES_PORT= - POSTGRES_DB= - POSTGRES_USER= - POSTGRES_PASSWORD= -``` - -## API -The api.py file creates a FastAPI in order to retrieve links statuses. -Run the command below: +## Api Key Features +1. **Link validation**: +Returns HTTP status codes for each link, along with other important information such as the parent URL, any warnings, and the date and time of the test. +![Fast API link_status](./images/link_status.png) +2. **Broken link categorization**: +Identifies and categorizes broken links based on status codes, including Redirection Errors, Client Errors, and Server Errors. +![Link categorization enpoint](./images/categorization.png) +3. **Deprecated links identification**: +Flags links as deprecated if they have failed for X consecutive tests, in our case X equals to 10. +Deprecated links are excluded from future tests to optimize performance. +![Fast API deprecated endpoint](./images/deprecated.png) +4. **Timeout management**: +Allows the identification of URLs that exceed a timeout threshold which can be set manually as a parameter in linkchecker's properties. +![Fast API timeout enpoint](./images/timeouts.png) +5. **Availability monitoring**: +When run periodically, the tool builds a history of availability for each URL, enabling users to view the status of links over time. +![Link validation enpoint](./images/val_history.png) + +## Container Deployment + +Set environment variables in Dockerfile to enable database connection. + +Run the following command: + +The app can be deployed as a container. +A docker-compose file has been implemented. + +Run ***docker-compose up*** to run the container + +***Helpful commands*** +To run the FastAPI locally run ``` python -m uvicorn api:app --reload --host 0.0.0.0 --port 8000 ``` -To view the service of the FastAPI on [http://127.0.0.1:8000/docs] - - -# Get current URL Status History -This endpoint returns the history of a specific URL. -Let say we have the status of a specific URL over time - -| id | url | validation_result | timestamp | -|-----|------------------------|-------------------|-------------------------| -| 1 | https://example.com | 200 OK | 2023-01-01 10:00:00+00 | -| 2 | https://wikipedia.com | 404 Not Found | 2023-01-01 10:00:05+00 | -| 3 | https://example.com | 200 OK | 2023-01-02 11:00:00+00 | -| 4 | https://wikipedia.com | 500 Server Error | 2023-01-02 11:00:05+00 | -| 5 | https://wikipedia.com | 200 OK | 2023-01-02 11:00:10+00 | +The FastAPI service runs on: [http://127.0.0.1:8000/docs] only if ROOTPATH is not set -Running the `/Single_url_status_history` endpoint for the -https://wikipedia.com and setting limit = 2 it will fetch the following result: +## Deploy linky at a path +You can set ROOTPATH env var to run the api at a path (default is at root) -| id | url | validation_result | timestamp | -|-----|------------------------|-------------------|-------------------------| -| 1 | https://wikipedia.com | 500 Server Error | 2023-01-02 11:00:05+00 | -| 2 | https://wikipedia.com | 404 Not Found | 2023-01-01 10:00:05+00 | - -This is the URL's history in descenting order in datetime - -# Docker -======= -## Deploy `linky` at a path - -You can set `ROOTPATH` env var to run the api at a path (default is at root) - -``` export ROOTPATH=/linky -``` - -## Docker - -A Docker instance must be running for the linkchecker command to work. ## CI/CD -A workflow is provided in order to run it as a cronological job once per week every Sunday Midnight -(However currently it is commemended to save running minutes since it takes about 80 minutes to complete) -It is necessary to use the **secrets context in gitlab in order to be connected to database +A CI/CD configuration file is provided in order to create an automated chronological pipeline. +It is necessary to define the secrets context using GitLab secrets in order to connect to the database. ## Roadmap - ### GeoHealthCheck integration -[GeoHealthCheck](https://GeoHealthCheck.org) is a component to monitor livelyhood of typical OGC services (WMS, WFS, WCS, CSW). It is based on the [owslib](https://owslib.readthedocs.io/en/latest/) library, which provides a python implementation of various OGC services clients. +[GeoHealthCheck](https://GeoHealthCheck.org) is a component to monitor livelyhood of typical OGC services (WMS, WFS, WCS, CSW). +It is based on the [owslib](https://owslib.readthedocs.io/en/latest/) library, which provides a python implementation of various OGC services clients. -## Soilwise-he project +## Technological Stack -This work has been initiated as part of the [Soilwise-he project](https://soilwise-he.eu/). -The project receives funding from the European Union’s HORIZON Innovation Actions 2022 under grant agreement No. 101112838. +1. **Core Language**: + - Python: Used for the linkchecker, API, and database interactions. +2. **Database**: + - PostgreSQL: Utilized for storing and managing information. + +3. **Backend Framework**: + - FastAPI: Employed to create and expose REST API endpoints, utilizing its efficiency and auto-generated components like Swagger. + +4. **Containerization**: + - Docker: Used to containerize the linkchecker application, ensuring deployment and execution across different environments. + +## Soilwise-he project +This work has been initiated as part of the Soilwise-he project. +The project receives funding from the European Union’s HORIZON Innovation Actions 2022 under grant agreement No. 101112838. \ No newline at end of file diff --git a/images/categorization.png b/images/categorization.png new file mode 100644 index 0000000000000000000000000000000000000000..2d785447eb7cd52ae940f3f25eea05becd407c94 GIT binary patch literal 18578 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 NqMWL1)eGa`e*p}ElotR1 literal 0 HcmV?d00001 diff --git a/images/link_status.png b/images/link_status.png new file mode 100644 index 0000000000000000000000000000000000000000..d8421e886b759f7b1d1cc0ee131eec9ef71414ae GIT binary patch literal 4992 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 IZrVQjA4z=M5dZ)H literal 0 HcmV?d00001 diff --git a/images/val_history.png b/images/val_history.png new file mode 100644 index 0000000000000000000000000000000000000000..b8758673075c6473d60a9f797ff7655cf27156d9 GIT binary patch literal 5586 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 A^#A|> literal 0 HcmV?d00001