Webserver i Node.js

Her følger en enkel webserver i Node.js som kan tilpasses slik man vil:

var html = "";
  html += "<!doctype html>\n";
  html += "<html>\n";
  html += "<head>\n";
  html += "<title>TITTEL</title>\n";
  html += "<meta charset='utf-8'>\n";
  html += "</head>\n";
  html += "<h1>TITTEL</h1>\n";
  html += "<p>INNHOLD</p>\n";
  html += "</html>\n";

var http = require("http");

var server = http.createServer(function(forespørsel, respons) {

  // Mal
  var side = html;

  // Behandling av forespørsel
  if (forespørsel.url === "/") {
    side = side.split("TITTEL").join("Front");
    side = side.split("INNHOLD").join("Hei! Dette er frontsiden.");

  } else if (forespørsel.url === "/kontakt") {
    side = side.split("TITTEL").join("Kontakt");
    side = side.split("INNHOLD").join("E-post: post@ovebakken.no");

  } else {
    side = side.split("TITTEL").join("Feil");
    side = side.split("INNHOLD").join("Forespurt side eksisterer ikke.");
  }

  // Send innhold
  respons.writeHead(200, {"Content-Type": "text/html"});
  respons.write(side);
  respons.end();
});
server.listen(80);

HTML-koden kan endres til hva som helst, og det er enkelt å legge til eller fjerne sider.

Portnummer her er 80, men kan også endres så lenge man velger en port som er ledig.

TBC

Funksjoner i Vue.js

I enhver "app" har man datamedlemmer som brukes i funksjoner for å lage nytt innhold.

methods

Her følger et eksempel hvor to tall skal legges sammen:

Tall 1: <input type="text" v-model:value="tall1">
Tall 2: <input type="text" v-model:value="tall2">
Svar: <input v-bind:value="svar()">

Det er to felt for å taste inn tall, og et felt for å vise svaret.

Funksjonen svar tar seg av arbeidet:

var app = new Vue({
  el: "#app",
  data: {
    tall1: 0,
    tall2: 0
  },
  methods: {
    svar: function() {
      return parseFloat(this.tall1) + parseFloat(this.tall2);
    }
  }
});

computed

Alternativt kan man gjøre som følger:

var app = new Vue({
  el: "#app",
  data: {
    tall1: 0,
    tall2: 0
  },
  methods: {
  },
  computed: {
    svar: function() {
      return parseFloat(this.tall1) + parseFloat(this.tall2);
    }
  }
});

Her er svar flyttet til computed i stedet for, dette muliggjør følgende:

Tall 1: <input type="text" v-model:value="tall1">
Tall 2: <input type="text" v-model:value="tall2">
Svar: <input v-bind:value="svar">

Her henvises det til svar som om dette er et datamedlem. Dette blir litt mer elegant.

TBC

Egne komponenter i Vue.js

Ofte er det kjekt å lage sine egne komponenter, som deretter kan brukes igjen og igjen.

Enveis

Her følger en enkel komponent som gir lite nytte, men som gjør oppsettet forståelig:

<tittel v-bind:tekst=tittel></tittel>
Vue.component("tittel", {
  props: ["tekst"],
  template: "<h1>{{tekst}}</h1>"
});
..
var app = new Vue({
  data: {
    tittel: "Min tittel her"
  }
});

I praksis er <h1> byttet med <tittel>, og når scriptet kjører byttes det bare tilbake.

Toveis

En mye nyttigere komponent er en som tar i mot informasjon:

Opplysning 1: <inndata v-model="opplysning1"></inndata>
Opplysning 2: <inndata v-model="opplysning2"></inndata>
..
Svar: <inndata v-bind="svar"></inndata>
Vue.component("inndata", {
  props: ["tekst"],
  template: `<input type="text" v-bind:value="tekst" v-on:input="$emit('input', $event.target.value)" >`
});

Her får man en fiks ferdig standard inndata-komponent som kan gjenbrukes alle steder. Man slipper da noe av den repetive kodeskrivingen og ekstraarbeidet når noe skal forandres senere.

TBC

Viktigste npm-kommandoer

Her følger en oversikt over de viktigste npm-kommandoene. Alle disse må kjøres fra prosjektets rotmappe hvis ikke annet er nevnt.

Oppsett

npm init

Man kjører denne kommandoen for et nytt prosjekt når man ønsker litt hjelp med å opprette en ny package.json:

Denne filen inneholder navn, beskrivelse, o.s.v. for prosjektet og brukes for å holde styr på alle avhengigheter som oppstår senere.

npm install pakkenavn –save

Ved å kjøre denne kommandoen lastes pakken ned og legges i en mappe ved navn node_modules. I tillegg lagres pakken som en avhengighet i package.json p.g.a. –save.

npm install

Ved å kjøre denne kommandoen opprettes node_modules med innhold, takket være package.json og dens lagrede avhengigheter.

TBC

Kom i gang med Vue.js

Om du ønsker å komme i gang med Vue.js er dette enkelt. I motsetning til hvordan ståa er for de fleste andre populære bibliotek/rammeverk som f.eks. Angular.js og Ember.js.

Man trenger kun en moderne nettleser, en teksteditor, samt ei HTML-fil med litt grunnleggende startinnhold:

<html>
  <head>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
  </head>
  <body>
    HTML-kode her ..
    <script>JavaScript-kode her ..</script>
  </body>
</html>

Det er riktignok anbefalt å ha litt kjennskap til HTML og JavaScript før man begynner, men dette kan man lære seg samtidig hvis man bare prøver.

Nedenfor følger de viktigste kodeeksemplene som er å finne i oppstartsguiden til Vue.js. Først er det litt HTML-kode, så JavaScript. Alle eksemplene er rimelig selvforklarende og gir et godt utgangspunkt for hvordan man skal ta i bruk en bestemt funksjonalitet.

Innhold

Enveis

<div id="app">
  {{ melding }}
</div>
var app = new Vue({
  el: '#app',
  data: {
    melding: 'Hallo Vue!'
  }
})

Toveis

<div id="app">
  {{ melding }}
  <input v-model="melding">
</div>
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hallo Vue!'
  }
})

Kontroll

if

<div id="app">
  <span v-if="synlig">Nå ser du meg</span>
</div>
var app = new Vue({
  el: '#app',
  data: {
    synlig: true
  }
})

for

<div id="app">
  <ol>    
    <li v-for="oppgave in oppgaver">
      {{ oppgaver.tekst }}
    </li>
  </ol>
</div>
var app = new Vue({
  el: '#app',
  data: {
    oppgaver: [
      { tekst: 'Lær JavaScript' },
      { tekst: 'Lær Vue' },
      { tekst: 'Lag noe kult!' }
    ]
  }
})

Hendelser

click

<div id="app">
  {{ melding }}
  <button v-on:click="reverserMelding">Reverser melding</button>
</div>
var app = new Vue({
  el: '#app',
  data: {
    melding: 'Hallo Vue.js!'
  },
  methods: {
    reverserMelding: function () {
      this.melding = this.melding.split('').reverse().join('');
    }
  }
})

wait og notify

For å slippe diverse dårlige/kreative løsninger som fort kan innebære mye knoting tilbyr Java wait() og notify() som gjør det enkelt for tråder å dele på de samme objektene.

Her er et enkelt og forståelig eksempel som tar utgangspunkt i det å være en vaskemaskin:

class Vaskemaskin {
 
   private static boolean klar = true;
   private static String klesvask = null;
 
   public synchronized void start(String møkketeKlesvask) throws InterruptedException {
      if (klesvask == null && klar) {
         klar = false; 
         klesvask = møkketeKlesvask;
         System.out.println("Starter vasking av " + klesvask);
         Thread.sleep(3000); // vi later som 3 sekund er 3 timer
         klar = true;
         notify(); // si i fra til ventende tråd
      } else {
         wait(); // vent
      }
   }
 
   public synchronized void tøm() throws InterruptedException {
      if (klesvask == null || !klar) {
         wait(); // vent
      } else {
         if (klesvask != null) {
            System.out.println("Tømmer vaskemaskin for " + klesvask);
            klesvask = null;
         }
         notify(); // si i fra til ventende tråd
      }
   }
}

Klassen Vaskemaskin tilbyr her bare to metoder – en for å starte en ny omgang og en for å tømme etterpå.

Begge er synkroniserte så programmet ville faktisk fungert uten notify() og wait(), problemet er da at tråden for å tømme vaskemaskinen stadig vil gjøre unødvendige kall på tøm().

For å teste denne klassen kan følgende benyttes:

private static Vaskemaskin vaskemaskin = new Vaskemaskin();

public static void main(String[] args) {

   // Sett på maskin
   new Thread(new Runnable() {
      public void run() {
         while (true) {
            try {
               vaskemaskin.start( klesvask() );
            } catch (InterruptedException ex) {}
         }
      }
   }).start();

   // Vent på maskin for å tømme
   new Thread(new Runnable() {
      public void run() {
         while (true) {
            try {
               vaskemaskin.tøm();
            } catch (InterruptedException ex) {}
         }
      }
   }).start();
}

public static String klesvask() {
 
   String[] klær = new String[] {
      "noen sokker", "litt håndklær", "ett dynetrekk",
      "noen putetrekk", "et par laken", "litt gensere", "noen bukser"
   };
 
   return klær[(int) (Math.random() * klær.length)];
}

.. som så gir et eller annet som ligner dette:

Starter vasking av litt håndklær
Tømmer vaskemaskin for litt håndklær
Starter vasking av noen sokker
Tømmer vaskemaskin for noen sokker
Starter vasking av litt håndklær
Tømmer vaskemaskin for litt håndklær
...

varargs

Som med C# og sikkert en rekke andre språk har også Java mulighet for å ha funksjoner/metoder som kan ta et ukjent antall innverdier.

Her er to slike funksjoner/metoder hvor man kan mate inn så mange String man vil:

public static void skrivUtMedMellomrom(String ... tekst) {
   String linje = "";
   for (String t : tekst) {
      linje += t + " ";
   }
   linje = linje.trim();
 
   System.out.println(linje);
}
public static void skrivUtMedMellomrom(int antallMellomrom, String ... tekst) {
   String linje = "";
   for (String t : tekst) {
      linje += t;
      for (int m = 0; m < antallMellomrom; m++) {
         linje += " ";
      }
   }
   linje = linje.trim();
 
   System.out.println(linje);
}

For å teste kan man for eksempel kjøre følgende:

public static void main(String[] args) {
   skrivUtMedMellomrom("Dette", "er", "en", "test!");
   skrivUtMedMellomrom(3, "I", "want", "cake!");
}

.. som gir:

Dette er en test!
I   want   cake!

String er ikke det eneste som er støttet selvsagt, alle typer kan benyttes.

Og signaturen trenger ikke være slik den er over – man har uendelig mange muligheter.

Synkronisering

Med asynkron programmering dukker ofte behovet opp for kontrollere hvilken tråd som bruker bestemte ressurser på et gitt tidspunkt.

Det er ganske vanlig å kun tillate èn tråd, i stedet for flere likt. Har man ingen kontroll risikerer man at oppdaterte/endrede data blir mangelfulle, at ting ikke blir gjort i riktig rekkefølge, o.s.v. Her kommer lock, Monitor, [Synchronization] med flere, inn i bildet.

Lock og Monitor

Vanligvis kommer man langt med lock som i praksis fungerer slik:

lock (this) // hvis dette er inni en privat metode
{
   ...
}

.. eller slik:

private object låsObjekt = new object(); // utenfor metoden
...
lock (låsObjekt) // hvis dette er inni en åpen metode
{
   ...
}

Og siden lock bare er en kortform for Monitor kan man visst i følge boka også kjøre på med følgende:

private object låsObjekt = new object(); // utenfor metoden
...
Monitor.Enter(låsObjekt) // inni åpen eller stengt metode
try
{
   ...
}
finally
{
   Monitor.Exit(låsObjekt)
}

Fordelen er visst at dette muliggjør større kontroll fordi Monitor blant annet har støtte for å kontakte ventende tråder.

[Synchronization]

Vil man ordne låsing for en hel klasse trenger man bare å plassere [Synchronization] fremfor klassedefinisjonen sin. Ulempen er at dette vil gjelde for absolutt alt innhold – til og med de metodene som ikke trenger det.

Asynkron programmering

Etter å ha brukt ca èn uke har jeg nå fått god oversikt over de forskjellige måtene man kan bedrive asynkron programmering på i NET. Og ikke bare har jeg fått repetert gammelt stoff, jeg har også fått lært mye nytt.

Hvorfor jeg ikke har brukt tid på dette før vet jeg ikke helt. Jeg har alltid hatt for vane å opprette egne tråder og ikke tenkt mer på det, det er jo så enkelt og intuitivt:

new Thread(() =>
{
   ...
}).Start();

Delegater

Først, for å i det hele tatt kunne komme i gang med å lære hvilke alternativer som fantes måtte jeg lære hvordan delegater fungerer: En delegat er simpelthen bare en egen type for å holde på referanser til funksjoner/metoder. Disse brukes fordi man ikke alltid kan benytte de aktuelle funksjonene/metodene direkte. En delegat har samme signatur som de funksjonene/metodene den skal kunne referere til.

Om vi først tar utgangspunkt i to enkle mattefunksjoner:

static double Pluss(double tall1, double tall2)
{
   return tall1 + tall2;
}
static double Minus(double tall1, double tall2)
{
   return tall1 - tall2;
}

.. så gir dette utgangspunktet for hvordan definisjonen til delegaten må se ut:

delegate double MatteFunksjon(double tall1, double tall2);

Deretter kan vi opprette en stk. for hver av de opprinnelige funksjonene:

MatteFunksjon PlussDelegat = new MatteFunksjon(Pluss);
MatteFunksjon MinusDelegat = Minus; // en kortere form

Disse delegatene, altså PlussDelegat og MinusDelegat, kan nå brukes hver gang man ønsker å benytte Pluss og eller Minus i en eller annen asynkron sammenheng.

IAsyncResult

Boka jeg bruker begynte dette temaet med å ta i bruk delegater og (I)AsyncResult.

Et eksempel på hvordan dette fungerer i praksis, med Pluss og PlussDelegat:

IAsyncResult plussAsyncResultat = PlussDelegat.BeginInvoke(2, 2, null, null);
while (!plussAsyncResultat.IsCompleted)
{
   ...
}
double svar = PlussDelegat.EndInvoke(plussAsyncResultat);

Her sendes tallene 2 og 2 til Pluss (som skal kjøre asynkront) via PlussDelegat, og det returneres en instans av AsyncResult via en IAsyncResult-referanse. Denne brukes helt til slutt for å hente ut svaret.

I mellomtiden venter man på at plussAsyncResultat skal bli ferdig.

En alternativ løsning kan være å benytte en egen funksjon/metode som tar seg av det som skal gjøres etter Pluss er ferdig.

I praksis kan man sause alt sammen inn i BeginInvokePlussDelegat:

IAsyncResult plussAsyncResultat = PlussDelegat.BeginInvoke(2, 2, new AsyncCallback(PlussResultat), new double[] {2,2});

.. hvor PlussResultat får ansvaret for å avslutte:

void PlussResultat(IAsyncResult iar)
{
   double[] tall = (double[]) iar.AsyncState;
   double svar = PlussDelegat.EndInvoke(iar);
   ...
}

Dermed blir det aldri noe venting i den opprinnelige tråden.

Thread

Min favoritt som er egne tråder er nok fortsatt det alternativet jeg liker best:

Thread tråd = new Thread(() =>
{
   double svar = Pluss(2, 2);
   ...
});
tråd.Start();

I stedet for anonyme funksjoner kan man selvsagt også bruke en funksjon/metode som allerede eksisterer, hvis den har riktig signatur:

Thread tråd = new Thread(FunksjonMedRiktigSignatur); // gitt funksjon må returnere void uten å ta inn parametere

.. eller:

Thread tråd = new Thread(FunksjonMedRiktigSignatur); // gitt funksjon må returnere void og akseptere èn stk. object inn
...
tråd.Start(new double[] {2, 2});

Det som kan være et problem her er at ikke noe returneres. En mulig løsning er da å lagre resultatet i en variabel opprettet utenfor tråden, men siden dette åpner for å få en «race condition» må man være påpasselig med hvordan man gjør det.

ThreadPool

Mye omtalte ThreadPool som ofte er anbefalt, men som jeg aldri har lært å like:

WaitCallback plussDelegatForTrådSamling = new WaitCallback((data) =>
{
   double svar = Pluss(2, 2);
   ...
});
ThreadPool.QueueUserWorkItem(plussDelegatForTrådSamling);

Også her kan man bruke en eksisterende funksjon/metode hvis noen passer:

WaitCallback minusDelegatForTrådSamling = new WaitCallback(FunksjonMedRiktigSignatur); // gitt funksjon må returnere void og ta inn èn stk. object
ThreadPool.QueueUserWorkItem(minusDelegatForTrådSamling, new double[] {2, 2});

Task

Et alternativ jeg har begynt å like er Task siden det gir lite kode:

Task.Factory.StartNew(() =>
{
   double svar = Pluss(2, 2);
   ...
});

Også her kan vi angi en eksisterende funksjon/metode hvis signaturen passer:

using (Task plussOppgave = Task.Factory.StartNew(FunksjonMedRiktigSignatur)) // gitt funksjon må returnere void uten å ta inn parametere
{
   while (!plussOppgave.IsCompleted)
   {
      ...
   }
}

Task er støttet fra og med versjon 4.0 av NET.

async og await

I motsetning til (I)AsyncResult, Thread, ThreadPool og Task så er async og await litt vanskelig å komme inn i til å begynne med.

Først et eksempel på hvordan funksjonen/metoden kan se ut her for at man skal kunne bruke den asynkront:

async void AsyncPlussResultat(double tall1, double tall2)
{
   await Task.Run(() =>
   {
      double svar = Pluss(tall1, tall2);
      ...
   });
}

.. som kan brukes med:

AsyncPlussResultat(2, 2);

(Man trenger riktignok ikke alltid mate inn data slik som her, man kan ha funksjoner/metoder som ikke tar parametere i det hele tatt.)

Som med Thread og ThreadPool er det også her litt kronglete å få fraktet data helt tilbake dit det opprinnelige kallet begynner. Men ved å bruke async og await med Task er det fullt mulig:

async Task<double> AsyncPluss(double tall1, double tall2)
{
   return await Task.Run(() =>
   {
      double svar = Pluss(tall1, tall2);
      return svar;
   });
}

.. hvor denne kalles ved å f.eks. bruke følgende:

Task<double> oppgave = AsyncPluss(2, 2);
while (!oppgave.IsCompleted)
{
   ...
}
double svar = oppgave.Result;

Altså får man også her returnert et svar, om man er villig til å vente litt.

Parallel

Siste alternativet (akkurat nå) er Parallel, her med bruk av Invoke:

Parallel.Invoke(() =>
{
   double svar = Pluss(2, 2);
   ...
},
() =>
{
   double svar = Minus(2, 2);
   ...
});

Parallel har også For og ForEach for å trygt kunne kjøre gjennom «collections», og er støttet fra og med 4.6 av NET.

Mal for klasse med egen ressursfrigjøring

Har man en klasse som benytter mye «managed» minne kan man selv implementere og sende kall til Dispose() for å frigjøre minnet med en gang man er ferdig.

Om man bruker «unmanaged» minne bør også dette frigjøres her, i tillegg må man implementere metoden ~NavnPåKlasse() som GC kan bruke i tillfelle man glemmer å sende kall til Dispose() på egenhånd.

Her er en ferdig mal som inneholder alt dette:

class Klasse : IDisposable
{
   private bool brukerFortsattRessurser = true;

   ~Klasse()
   {
      // Frigjør bare 'unmanaged' ressurser:
      FrigjørRessurser(false);

      Console.WriteLine("~Klasse() kalt");
   }

   public void Dispose()
   {
      // Frigjør både 'managed' og 'unmanaged' ressurser:
      FrigjørRessurser(true);

      // Fortell GC at ~Klasse() ikke trengs å kjøres:
      GC.SuppressFinalize(this);

      Console.WriteLine("Dispose() kalt");
   }

   private void FrigjørRessurser(bool beggeTyper)
   {
      // Frigjør både 'managed' og 'unmanaged' ressurser hvis dette ikke er gjort:
      if (brukerFortsattRessurser)
      {
         // Frigjør 'managed' ressurser her:
         if (beggeTyper)
         {
            // ..
         }

         // Frigjør 'unmanaged' ressurser her
         // ..

         brukerFortsattRessurser = false;
      }

      Console.WriteLine("FrigjørRessurser(" + beggeTyper + ") kalt");
   }
}

Her bør hver nye metode man lager først kontrollere at brukerFortsattRessurser er sann.

Er Dispose() allerede kjørt burde nye metodekall gi unntak (altså «exception»).