CORS Range request op service.pdok.nl

Het NGR server host grote bestanden zoals de 1.79 GiB inspireadressen.zip, aka “INSPIRE Download Service van BAG - INSPIRE”. Deze URL support zowel CORS als Range requests, maar niet in combinatie.

Huidige response op HEAD request
curl -I 'https://geodata.nationaalgeoregister.nl/inspireadressen/extract/inspireadressen.zip'
HTTP/1.1 200 
Date: Thu, 09 Sep 2021 17:25:03 GMT
Last-Modified: Mon, 09 Aug 2021 06:52:36 GMT
Content-Disposition: attachment; filename=inspireadressen.zip
Content-Range: bytes 0-1931988030/1931988031
Accept-Ranges: bytes
Content-Type: application/x-zip-compressed
Content-Length: 1931988031
X-Cnection: close
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS, HEAD
Access-Control-Max-Age: 1000
Access-Control-Allow-Headers: SOAPAction,X-Requested-With,Content-Type,Origin,Authorization,Accept
X-Cnection: close

Voor een CORS request met range wordt een Preflight request uitgevoerd dat er zo uit ziet:

Sample HTTP Preflight Request & Response
OPTIONS /inspireadressen/extract/inspireadressen.zip HTTP/1.1
Host: geodata.nationaalgeoregister.nl
Access-Control-Request-Method: GET
Access-Control-Request-Headers: range
Origin: https://www.example.org

welke een 200 OK of 204 No Content terug zou moeten geven:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
Access-Control-Allow-Headers: range
Sample HTTP CORS Range Request & Response na Preflight
GET /inspireadressen/extract/inspireadressen.zip HTTP/1.1
Host: geodata.nationaalgeoregister.nl
Range: bytes=0-1
Origin: https://www.example.org

met als antwoord:

HTTP/1.1 206 Partial Content
Date: Thu, 09 Sep 2021 17:41:55 GMT
Last-Modified: Mon, 09 Aug 2021 06:52:36 GMT
Content-Disposition: attachment; filename=inspireadressen.zip
Content-Range: bytes 0-1/1931988031
Accept-Ranges: bytes
Content-Type: application/x-zip-compressed
Content-Length: 2
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
Access-Control-Expose-Headers: Content-Range, Content-Disposition

PK
Huidige Preflight Request & Response
curl -i 'https://geodata.nationaalgeoregister.nl/inspireadressen/extract/inspireadressen.zip' -X OPTIONS -H 'Access-Control-Request-Method: GET' -H 'Access-Control-Request-Headers: range' -H 'Origin: https://www.example.org'

echter een 403:

HTTP/1.1 403 
Date: Thu, 09 Sep 2021 17:45:29 GMT
Content-Type: text/plain; charset=UTF-8
Content-Length: 0
X-Cnection: close
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS, HEAD
Access-Control-Max-Age: 1000
Access-Control-Allow-Headers: SOAPAction,X-Requested-With,Content-Type,Origin,Authorization,Accept
X-Cnection: close
Huidige CORS Range GET Request & Response op de resource
curl -i 'https://geodata.nationaalgeoregister.nl/inspireadressen/extract/inspireadressen.zip' -H 'Range: bytes=0-1' -H 'Origin: https://www.example.org'

In de Response mist Access-Control-Expose-Headers: Content-Range, Content-Disposition:

HTTP/1.1 206 
Date: Thu, 09 Sep 2021 17:47:39 GMT
Last-Modified: Mon, 09 Aug 2021 06:52:36 GMT
Content-Disposition: attachment; filename=inspireadressen.zip
Content-Range: bytes 0-1/1931988031
Accept-Ranges: bytes
Content-Type: application/x-zip-compressed
Content-Length: 2
X-Cnection: close
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS, HEAD
Access-Control-Max-Age: 1000
Access-Control-Allow-Headers: SOAPAction,X-Requested-With,Content-Type,Origin,Authorization,Accept
X-Cnection: close

PK

Technische details:

Ik wilde enkel de laatste 22 bytes requesten waar de End Of Central Directory Record (EOCD) staat om daarna alle Central directory entries te downloaden en enkel de 18.35 MiB van 9999WPL08072021.zip op te halen, ofwel < 19 MiB request i.p.v. > 1.79 GiB. Dit kan allemaal met zip.js (na wat patching).

Demo (met CORS proxy):

Zie CORS Range demo - Broersma (in een paar regels code) welke de inspireadressen.zip Central directory leest en laat zien met behulp van een CORS proxy (https://cors-anywhere.herokuapp.com).

1 like

PS Naast het niet sturen van een Server:, en de dubbele X-Cnection: close1 stuurt de server ook:

Access-Control-Allow-Headers: SOAPAction,X-Requested-With,Content-Type,Origin,Authorization,Accept

waar Origin, Content-Type en Accept wat vreemd zijn, gezien de laatste twee in de CORS-safelisted request header staan, en Origin natuurlijk sowieso wordt meegestuurd in CORS context.

  1. vast mooie caching server welke de streaming replace van Connection: naar X-Cnection: uitvoert

Bedankt voor het melden

Dit is inderdaad incorrect gedrag, we gaan kijken of we dit kunnen fixen.
Wel kan ik zeggen dat we bezig zijn om onze services van geodata.nationaalgeoregister.nl naar service.pdok.nl te migreren.

En voor de inspireadressen komt er, waarschijnlijk in oktober, een nieuwe service beschikbaar op service.pdok.nl

Grappig dat je ook een online ZIP gedeeltelijk wil lezen. Bij het maken van onze BGT lader had ik de functionaliteit om een online ZIP gedeeltelijk te downloaden ook gebouwd om de BGT plaatsbepalingspunten niet te hoeven downloaden. Deze ZIP is nog een stukje groter dan de 1,8 GB van de BAG, namelijk 47 GB waarvan ik 28 GB aan plaatsbepalingspunten wou overslaan :sweat_smile:

Deze tool heb ik niet in JavaScript maar in Java geschreven - dus geen last van CORS headers, maar toevallig ook van een bug in de commons-compress ZIP-bibliotheek.

In Java code kan je deze BAG ZIP zo lezen zonder die helemaal te hoeven downloaden:

import nl.b3p.brmo.util.http.HttpSeekableByteChannel;
import org.apache.commons.compress.archivers.zip.*;
...
final URI uri = new URI("https://geodata.nationaalgeoregister.nl/inspireadressen/extract/inspireadressen.zip");
try(
        HttpSeekableByteChannel channel = new HttpSeekableByteChannel(uri);
        ZipFile zipFile = new ZipFile(channel, uri.toString(),"UTF8", false, true);
) {
    for (Iterator<ZipArchiveEntry> it = zipFile.getEntries().asIterator(); it.hasNext();) {
        final ZipArchiveEntry entry = it.next();
        System.out.println(entry.getName());
    }
}

Er zijn dan 3 HTTP requests nodig om 624 bytes te lezen.

@bwb een command-line tool die unzip met http range requests doet zoals je hier om vraagt kan je denk ik met nodejs en jouw code wel maken. Die was ik ook van plan te maken met mijn Java-code voor HTTP range requests en commons-compress. Vreemd dat in Nederland zo vaak enorme ZIP’s online worden gezet waar je vaak dingen van wil overslaan…

Verder wens ik je veel succes met XSL, vaak heb ik later spijt gekregen van het toepassen daarvan, maar leuk functioneel programmeren is het soms wel :wink:

@Wouter_Remijn:
Staat er ergens een config online / op GitHub, kan ik ergens mee helpen? (is het nginx, apache of een varnish / andere caching server?)
Wat voor inspireadressen-service moet ik aan denken, is dit al ergens besproken op geoforum?

@matthijsln:

Onder de 624 content response bytes kom je niet uit: 1 HEAD ‘0 bytes’ + 1 GET EOCD (laatste 22 bytes) en dan 1 GET met hele Central directory van 608 bytes = 624 totaal, maar ik zie dat zip.js het laatste request 630 bytes fetched, nl. opnieuw de EOCD … argh (nieuwe :bug: ingediend :wink: ).
Het hele punt is zooi static op GitHub gooien zodat burgers en/of collega’s de tooling kunnen gebruiken, al is het deno idee van gildas-lormeau eigenlijk wel een goed excuus om met deno te gaan spelen :slight_smile: .
Geen Java fan hier, als het niet strikt noodzakelijk is weiger ik, helaas eind vorig jaar hele EML 210 generator met JAXB gemaakt :scream:.
Mijn use cases:

  1. Bij data.overheid.nl heb ik ook nog een verzoek uitstaan om zowel de API als downloads met CORS en range te kunnen benaderen, al heb ik ook zelf al even een CORS proxy in een paar regels nginx gebouwd. Want ik verwacht geen spoedige fix. Dit allemaal zodat ik uit de 50 MB ZIP twee EML bestanden (kandidatenlijst + uitslag) kan omzetten naar CSV (het OSV-4-3 formaat).
  2. Ik wil ook wat meer praktische tooling online zetten, bijv. een woonplaatsen extractor op github.com/Rijksoverheid. Ik zie dat de BAG ook een API heeft (met encoding issues) maar ik vraag me af of ik de API key online kan plaatsen (op een GitHub pages) en de API ook CORS ondersteund (dit laatste lijkt zonder API key wel het geval).
De XSLT maak ik met xmlstarlet, dan valt het wel mee, zie deze bijv.
xmlstarlet sel -C -T -N xb="http://www.kadaster.nl/schemas/bag-verstrekkingen/extract-deelbestand-lvc/v20090901" -N product_LVC="http://www.kadaster.nl/schemas/bag-verstrekkingen/extract-producten-lvc/v20090901" -N bag_LVC="http://www.kadaster.nl/schemas/imbag/lvc/v20090901" -N bagtype="http://www.kadaster.nl/schemas/imbag/imbag-types/v20090901" -t -m '/xb:BAG-Extract-Deelbestand-LVC/xb:antwoord/xb:producten/product_LVC:LVC-product/bag_LVC:Woonplaats[bag_LVC:woonplaatsStatus/text()="Woonplaats aangewezen" and bag_LVC:aanduidingRecordInactief="N" and number(bag_LVC:tijdvakgeldigheid/bagtype:begindatumTijdvakGeldigheid)<2021101100000000 and (not(bag_LVC:tijdvakgeldigheid/bagtype:einddatumTijdvakGeldigheid) or number(bag_LVC:tijdvakgeldigheid/bagtype:einddatumTijdvakGeldigheid) > 2021101100000000)]' -c 'bag_LVC:woonplaatsNaam/text()' -n

wat de volgende output / XSLT geeft:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xb="http://www.kadaster.nl/schemas/bag-verstrekkingen/extract-deelbestand-lvc/v20090901" xmlns:product_LVC="http://www.kadaster.nl/schemas/bag-verstrekkingen/extract-producten-lvc/v20090901" xmlns:bag_LVC="http://www.kadaster.nl/schemas/imbag/lvc/v20090901" xmlns:bagtype="http://www.kadaster.nl/schemas/imbag/imbag-types/v20090901" version="1.0">
  <xsl:output omit-xml-declaration="yes" indent="no" method="text"/>
  <xsl:template match="/">
    <xsl:for-each select="/xb:BAG-Extract-Deelbestand-LVC/xb:antwoord/xb:producten/product_LVC:LVC-product/bag_LVC:Woonplaats[bag_LVC:woonplaatsStatus/text()=&quot;Woonplaats aangewezen&quot; and bag_LVC:aanduidingRecordInactief=&quot;N&quot; and number(bag_LVC:tijdvakgeldigheid/bagtype:begindatumTijdvakGeldigheid)&lt;2021101100000000 and (not(bag_LVC:tijdvakgeldigheid/bagtype:einddatumTijdvakGeldigheid) or number(bag_LVC:tijdvakgeldigheid/bagtype:einddatumTijdvakGeldigheid) &gt; 2021101100000000)]">
      <xsl:copy-of select="bag_LVC:woonplaatsNaam/text()"/>
      <xsl:value-of select="'&#10;'"/>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

PS 20211011 is de magische dag van kandidaatstelling voor de gemeentelijke herindelingsverkiezingen van 24 november 2021 :slight_smile: .

Waarschijnlijk zal de inspireadressen.zip er ongeveer uit gaan zien als de ZIP die je nu nog (tot 1 oktober) kan downloaden van https://extracten.bag.kadaster.nl/lvbag/extracten/Nederland%20LVC/ . Dit vanwege uitfasering van BAG 1.0 naar BAG 2.0. Dus ik zou niet te veel code schrijven gebaseerd op de BAG 1.0.

Het probleem is dan meer XML dan Java denk ik :wink:

xmlstarlet is een fijne tool die ik ook graag gebruik. Maar voor basisregistraties vind ik persoonlijk het handigst om zo snel mogelijk uit de XML wereld te stappen en gebruik te maken van databases.

Voor de BAG 1.0 werkt je xmlstarlet commando goed maar voor BAG 2.0 moet je hem wat aanpassen.

@bwb, zoals Wouter Remijn ook reeds aangaf gaat de atom service voor inspire adressen binnenkort gemigreerd worden naar service.pdok.nl, waarna de huidige inspire adressen atom service op termijn uitgefasseerd zal gaan worden. zie ook Herziene INSPIRE Adressen en Gebouwen services en data - PDOK
Het goede nieuws is dat de nieuwe atom service wel de combinatie CORS en Range ondersteunt. Gezien we spoedig gaan migreren is het nog wel de vraag of er nog effort in gestoken moet worden om dit werkend te krijgen voor onze ‘oude’ PDOK services

Wat een :tada:! Zou dus deze maand (volgende week?) moeten gebeuren?

Met xmlstarlet is iets snel gemaakt, de BAG 2.0 versie is ook makkelijk te doen, wel rare 'uri' namespaces zonder schema
xmlstarlet sel -C -T -N sl-bag-extract="http://www.kadaster.nl/schemas/lvbag/extract-deelbestand-lvc/v20200601" -N sl="http://www.kadaster.nl/schemas/standlevering-generiek/1.0" -N Objecten="www.kadaster.nl/schemas/lvbag/imbag/objecten/v20200601" -N Historie="www.kadaster.nl/schemas/lvbag/imbag/historie/v20200601" -t -m '/sl-bag-extract:bagStand/sl:standBestand/sl:stand/sl-bag-extract:bagObject/Objecten:Woonplaats[Objecten:status/text()="Woonplaats aangewezen" and number(translate(Objecten:voorkomen/Historie:Voorkomen/Historie:beginGeldigheid/text(),"-",""))<20211011 and (not(Objecten:voorkomen/Historie:Voorkomen/Historie:eindGeldigheid) or translate(Objecten:voorkomen/Historie:Voorkomen/Historie:eindGeldigheid/text(),"-","")>20211011)]' -s 'A:T:U' 'Objecten:naam/text()' -c 'Objecten:naam/text()' -n

wat de volgende output / XSLT geeft:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:sl-bag-extract="http://www.kadaster.nl/schemas/lvbag/extract-deelbestand-lvc/v20200601" xmlns:sl="http://www.kadaster.nl/schemas/standlevering-generiek/1.0" xmlns:Objecten="www.kadaster.nl/schemas/lvbag/imbag/objecten/v20200601" xmlns:Historie="www.kadaster.nl/schemas/lvbag/imbag/historie/v20200601" version="1.0">
  <xsl:output omit-xml-declaration="yes" indent="no" method="text"/>
  <xsl:template match="/">
    <xsl:for-each select="/sl-bag-extract:bagStand/sl:standBestand/sl:stand/sl-bag-extract:bagObject/Objecten:Woonplaats[Objecten:status/text()=&quot;Woonplaats aangewezen&quot; and number(translate(Objecten:voorkomen/Historie:Voorkomen/Historie:beginGeldigheid/text(),&quot;-&quot;,&quot;&quot;))&lt;20211011 and (not(Objecten:voorkomen/Historie:Voorkomen/Historie:eindGeldigheid) or translate(Objecten:voorkomen/Historie:Voorkomen/Historie:eindGeldigheid/text(),&quot;-&quot;,&quot;&quot;)&gt;20211011)]">
      <xsl:sort order="ascending" data-type="text" case-order="upper-first" select="Objecten:naam/text()"/>
      <xsl:copy-of select="Objecten:naam/text()"/>
      <xsl:value-of select="'&#10;'"/>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

Ben al wel paar leuke bugs tegengekomen, silent fail van function:document bug in chromium en dezelfde function:document komt ongeveer als enige request niet voor als FetchEvent langs bij een ServiceWorker in Firefox (kan soortgelijke bugreport nog niet vinden).

Het probleem was meer JAXB :slight_smile:

https://www.pdok.nl/-/herziene-inspire-adressen-en-gebouwen-services-en-data:

Vanaf oktober zullen de WMS en Atomfeed worden voorzien van nieuwe URL’s.

Gaat de URL verhuizing ook echt nog in oktober 2021 plaatsvinden?
Op Downloads - PDOK staat nl. nog steeds de link naar https://geodata.nationaalgeoregister.nl/inspireadressen/atom/inspireadressen.xml

De verwachting is dat de atom service nog wel deze week live gaat. Momenteel zit hij in de test fase. Voor de WMS service zal dit helaas nog wat langer duren.

De link naar de zipfile zelf (niet per se de feed waar die in staat) staat al op BAG 2.0 Extract gebruiken? Download de bestanden hier - Kadaster.nl zakelijk onder “Kosteloze download BAG 2.0 extract” en is https://service.pdok.nl/kadaster/adressen/atom/v1_0/downloads/lvbag-extract-nl.zip

Het xmlstarlet commando kan iets simpeler en officieel moet je “niet BAG” voorkomens filteren (ook al zijn er op dit moment nu geen “niet BAG” voorkomens (versies) van woonplaatsen), bijvoorbeeld:

xmlstarlet sel -T \
  -N obj=www.kadaster.nl/schemas/lvbag/imbag/objecten/v20200601 \
  -N hist=www.kadaster.nl/schemas/lvbag/imbag/historie/v20200601  \
  -t -m "//obj:Woonplaats[obj:status = 'Woonplaats aangewezen' and not(obj:voorkomen//tijdstipNietBagLV) and number(translate(obj:voorkomen//hist:beginGeldigheid,'-','')) < 20211011 and (not(obj:voorkomen//hist:eindGeldigheid) or number(translate(obj:voorkomen//hist:eindGeldigheid,'-','')) > 20211011)]" \
  -s "A:T:U" "obj:naam" -v "obj:naam" -n \
  9999WPL08102021-000001.xml
1 like

De nieuwe atom service staat nu live

Helaas mislukt alleen de Preflight Request op de zip:

curl -i 'https://service.pdok.nl/kadaster/adressen/atom/v1_0/downloads/lvbag-extract-nl.zip' -X OPTIONS -H 'Access-Control-Request-Method: GET' -H 'Access-Control-Request-Headers: range' -H 'Origin: https://www.example.org'

ofwel:

OPTIONS /kadaster/adressen/atom/v1_0/downloads/lvbag-extract-nl.zip HTTP/1.1
Host: service.pdok.nl
Access-Control-Request-Method: GET
Access-Control-Request-Headers: range
Origin: https://www.example.org

geeft:

HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: GET, OPTIONS, HEAD
Access-Control-Allow-Origin: *
Content-Length: 0
Date: Fri, 05 Nov 2021 11:08:16 GMT
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

Waar range in de Access-Control-Allow-Headers mist.

Dank voor de obj:voorkomen//tijdstipNietBagLV opmerking!
Verder kan het inderdaad simpeler als je // gebruikt, echter worden XSLT’s daar flink trager van, omdat ik vaak met grote XML’s te maken heb probeer ik // ten alle tijden te vermijden. De -v maakt iets minder mooie XSLT’s, zodoende gebruik ik liever -c.