På Version2 er vi godt i gang med at automatisere valget af emneord til nye artikler, i hvert fald i den grad, som vores machine learning-algoritme Naive Bayes kan levere varen.
I sidste artikel fik vi optimeret algoritmen og pressede seks ekstra procentpoint ud af algoritmen, i hvert fald når emneordet var ‘sundheds-it.’
I mellemtiden har vi forbedret algoritmen lidt mere, ved at tælle overskrifter to gange, som vi også var inde på i sidste artikel. Vi prøvede også at tilføje artiklernes forfatternavne, men det trak en anelse ned i forudsigelserne - så det droppede vi igen. Version2’s journalister er åbenbart ikke fagidioter, kan man måske konkludere, eller så har der været mange forskellige igennem i årenes løb.
Nu er tiden kommet til at prøve det hele i virkeligheden, og se, om det egentlig kan bruges af dem, som det er tiltænkt - Version2’s journalister.
Vi vil implementere emneords-algoritmen i Version2’s CMS-system som en bookmarklet - en stump Javascript, der kan placeres i et link i et browser-bookmark. Så slipper vi for at overbevise webudviklerne om, at de skal smide vores tossede kode ind i deres system, og samtidigt er det nemmere end at lave en browser-extension.
Det er det rene skygge-it. Men den slags kan jo også have sin charme, indenfor rimelighedens grænser, altså.
Vores 'user story' lyder sådan her:
Journalisten har en ny, færdigskrevet artikel åben i redigerings-modus i CMS'et, og klikker på bookmarkletten i browserens bogmærkelinje. Derved sender bookmarkletten overskrifter og artikeltekst til serveren via et ajax-kald. Machine learning-algoritmen gør sit arbejde og returnerer bud på emneord til bookmarkletten, som derefter opmærker emneordene i CMS'ets dialogboks med gul baggrundsfarve. Så kan journalisten bruge forslagene eller ej, ved at sætte et flueben i en krydsboks - sådan som det ser ud i illustrationen i toppen af denne artikel.
Vi strikker et hjemmelavet certifikat
Vores CMS kører HTTPS, naturligvis, og reglen for bookmarklets er, som med eksterne scripts i almindelighed, at man ikke kan blande krypterede og ukrypterede kilder.
Vi skal med andre ord sætte en HTTPS-server op, som forsyner vores bookmarklet med bud på emneord.
Vi har ikke brug for stor server og det konfigureringsbesvær, der ofte følger med. HTTP-serveren vi benytter her, er en lille én, der følger med Javas udviklingsmiljø (JDK), men som ikke er officielt en del af klassebibliotekerne (JRE). Den befinder sig dog i et ‘jdk’-modul, og er dermed et velsignet medlem af JDK’et. Server-koden er open source, ligesom klassebibliotekerne i øvrigt.
For at skabe en SSL-forbindelse skal vores server have et certifikat. Her laver vi bare vores eget. Det betyder, at certifikatet ikke er underskrevet af et rodcertifikat, som ‘rigtige’ certifikater er, og at brugeren af bookmarkletten skal foretage en ‘sikkerhedsundtagelse’ i browseren.
Vi laver certifikatet med Java-værktøjet Keytool, som ligger i ‘bin’-mappen i Javas distribution. Jeg åbner et terminalvindue og starter programmet. På min pc ser det sådan ud:
C:\Program Files\Java\jre-9.0.4\bin>keytool -genkey -keyalg RSA -alias webservice -keystore C:\Users\tan\Desktop\selfsigned.jks -validity 365 -keysize 2048
– hvor det hele skal stå på samme linje.
Nu stiller programmet en række spørgsmål, der skal besvares.
Enter keystore password: Re-enter new password: What is your first and last name? (Unknown): Tania Andersen What is the name of your organizational unit? (Unknown): Version2 What is the name of your organization? (Unknown): MI What is the name of your City or Locality? (Unknown): Copenhagen What is the name of your State or Province? (Unknown): What is the two-letter country code for this unit? (Unknown): DK Is CN=Tania Andersen, OU=Version2, O=MI, L=Copenhagen, ST=Unknown, C=DK correct? (no): yes
Herefter kvitterer programmet med at generere en privat nøgle på mit skrivebord, med navnet selfsigned.jks.
Så kan vi sætte serveren op, således:
private static final String SUN_X509_ALGORITME = "SunX509"; public static void main(String[] args) throws IOException, Exception, NoSuchAlgorithmException { // Opsæt og start webserveren. HttpsServer httpsServer = HttpsServer.create( new InetSocketAddress(8000), 0); KeyStore ks = KeyStore.getInstance("JKS"); FileInputStream fis = new FileInputStream( "C:\\Users\\tan\\Desktop\\selfsigned.jks"); char[] password = new char[] { 'p', '1', 'z', 'z', 'a', 'r', 'e', 's', 't' }; // Char-array benyttes i stedet for String, // for at undgå at passwordet ender i String-poolen. ks.load(fis, password); KeyManagerFactory kmf = KeyManagerFactory .getInstance(SUN_X509_ALGORITME); kmf.init(ks, password); TrustManagerFactory tmf = TrustManagerFactory .getInstance(SUN_X509_ALGORITME); tmf.init(ks); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
Vi skaber en ny server med kaldet HttpsServer.create
, som tager en socket-portadresse som parameter. Her sætter vi den til port 8000.
Så skaber vi en Keystore
, som holder på vores private key, og indlæser vores nøgle selfsigned.jks
fra før, med en FileInputStream
, sammen med det password, vi brugte da vi skabte privat-nøglen.
Vi skal også bruge en KeyManagerFactory
(kmf) og en TrustManagerFactory
(tmf). Nu skal vi have et SSLContext
-objekt, som vi får med metoden SSLContext.getInstance("TLS")
, hvor vi beder om TLS-protokollen - vore dages udgave af SSL.
Nu initieres SSLContexten med metoden sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers())
. Så kan vi konfigurere vores server. Det ser sådan ud:
httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext) { public void configure(HttpsParameters params) { try { SSLContext c = SSLContext.getDefault(); SSLParameters defaultSSLParameters = c .getDefaultSSLParameters(); params.setSSLParameters(defaultSSLParameters); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } });
Nu er serveren konfigureret til SSL med vores hjemmelavede certifikat.
(Man kunne måske have ønsket sig, at Javas api-designere havde gjort det lidt nemmere for os. Jeg har set et Python-eksempel på det samme, og det fyldte fire linjer. Men sådan er det nu en gang i Java-verdenen.)
Nu skal der skabes en ‘context’, som er en request-handler, der knyttes til en bestemt url. Det ser sådan ud i vores eksempel:
httpsServer.createContext("/apps/emneord", new Servicehandler()); httpsServer.setExecutor(null); // creates a default executor httpsServer.start(); LocalDate now = LocalDate.now(); now.format(DateTimeFormatter.ISO_DATE); System.out.println("Starter webtjenesten. " + ZonedDateTime.now().format(FORMATTER));
Parameteren ‘new Servicehandler()‘ er en instans af interfacet HttpHandler, som har metoden ‘handle‘, der skaber HTTP-svaret. Det ser sådan ud i vores eksempel:
static class Servicehandler implements HttpHandler { private Tester tester = new Tester(); public void handle(HttpExchange exchange) throws IOException { InputStream is = exchange.getRequestBody(); final String requestBody = new String(is.readAllBytes(), "UTF-8"); String response = findEmneord(requestBody); final Headers responseHeaders = exchange.getResponseHeaders(); responseHeaders.put("Access-Control-Allow-Origin", List.of("*")); responseHeaders.put("Content-Type", List.of("text/plain")); exchange.sendResponseHeaders(200, response.length()); OutputStream os = exchange.getResponseBody(); os.write(response.getBytes()); os.close(); } }
Her henter vi request-body'en ud af HttpExchange-parameteren med kaldet exchange.getRequestBody
. Vi kalder derefter metoden findEmneord
. Tilbage i artiklen i april havde vi en metode, der hed testArtikel
, og vores findEmneord
-metode gennemløber blot testArtikel
med de 50 mest anvendte emneord. Hvis testArtikel
vender tommelfingeren op på et givent emne, sender vi emneordet tilbage til vores bookmarklet.
For at bookmarkletten kan få lov at tilgå CMS-websiden, skal vi sætte response-headeren Access-Control-Allow-Origin til en asterisk. Det gøres i linjen responseHeaders.put("Access-Control-Allow-Origin", List.of("*"))
.
Kodeeksemplet kan downloades fra Google Drive, da det fylder for meget for Gitlab - det er modellens estimat-tabeller, der fylder en del. (Google Drive kan sige nogle fjollede ting undervejs, men bare tryk 'download.'). Når serveren er oppe og køre, via det medfølgende batch-script, kan en testside kan tilgås med url'en: https://localhost:8000/apps/emneord/test. (Den del af koden er udeladt i eksemplet her i artiklen.)
I en kommende artikel ser vi nærmere på udviklingen af bookmarkletten, der modtager svaret fra vores server, og syr det ind i vores CMS. Bliv på kanalen.

...men det er dyrt at lave god journalistik. Derfor beder vi dig overveje at tegne abonnement på Version2.
Digitaliseringen buldrer derudaf, og it-folkene tegner fremtidens Danmark. Derfor er det vigtigere end nogensinde med et kvalificeret bud på, hvordan it bedst kan være med til at udvikle det danske samfund og erhvervsliv.
Og der har aldrig været mere akut brug for en kritisk vagthund, der råber op, når der tages forkerte it-beslutninger.
Den rolle har Version2 indtaget siden 2006 - og det bliver vi ved med.