Linux Professional Institute Learning Logo.
Ugrás a fő tartalomra
  • Főoldal
    • Minden erőforrás
    • LPI Tananyagok
    • Legyen ön is közreműködő
    • Kiadói partnereink
    • Legyen Ön is kiadói partnerünk
    • Rólunk
    • GYIK
    • Külső munkatársak
    • Kapcsolat
  • LPI.org
035.2 Lecke 2
Témakör 031: Szoftverfejlesztés és webtechnológiák
031.1 A szoftverfejlesztés alapjai
  • 031.1 Lecke 1
031.2 A webes alkalmazások architektúrája
  • 031.2 Lecke 1
031.3 A HTTP alapja
  • 031.3 Lecke 1
Témakör 032: A HTML-dokumentum jelölései
032.1 A HTML-dokumentum szerkezete
  • 032.1 Lecke 1
032.2 A HTML-dokumentumok hierarchiája és szemantikája
  • 032.2 Lecke 1
032.3 HTML referenciák és beágyazott erőforrások
  • 032.3 Lecke 1
032.4 A HTML űrlapok
  • 032.4 Lecke 1
Témakör 033: CSS - A tartalom formázása
033.1 CSS alapok
  • 033.1 Lecke 1
033.2 CSS szelektorok és a formázás alkalmazása
  • 033.2 Lecke 1
033.3 CSS formázás
  • 033.3 Lecke 1
033.4 CSS Dobozmodell és elrendezés
  • 033.4 Lecke 1
Témakör 034: JavaScript programozás
034.1 JavaScript végrehajtás és szintaxis
  • 034.1 Lecke 1
034.2 JavaScript adatstruktúrák
  • 034.2 Lecke 1
034.3 JavaScript vezérlési struktúrák és funkciók
  • 034.3 Lecke 1
  • 034.3 Lecke 2
034.4 A weboldal tartalmának és formázásának módosítása JavaScripttel
  • 034.4 Lecke 1
Témakör 035: NodeJS szerverprogramozás
035.1 NodeJS alapok
  • 035.1 Lecke 1
035.2 NodeJS Express alapok
  • 035.2 Lecke 1
  • 035.2 Lecke 2
035.3 SQL alapok
  • 035.3 Lecke 1
How to get certified
  1. Témakör 035: NodeJS szerverprogramozás
  2. 035.2 NodeJS Express alapok
  3. 035.2 Lecke 2

035.2 Lecke 2

Tanúsítvány:

Web Development Essentials

Verzió:

1.0

Témakör:

035 NodeJS szerverprogramozás

Fejezet:

035.2 NodeJS Express alapok

Lecke:

2/2

Bevezetés

A webszerverek nagyon sokoldalú mechanizmusokkal rendelkeznek a kliensek kéréseire adott válaszok előállításához. Bizonyos kérések esetén elég, ha a webszerver statikus, feldolgozatlan választ ad, mivel a kért erőforrás minden kliens számára ugyanaz. Ha például egy kliens egy mindenki számára elérhető képet kér, akkor elég, ha a szerver elküldi a képet tartalmazó fájlt.

Ha a válaszok dinamikusan generálódnak, akkor lehet, hogy jobban kell strukturálni őket, mintha írnánk néhány egyszerű szerverszkriptet. Ilyen esetekben célszerű, ha a webszerver képes egy teljes dokumentumot generálni, amelyet a kliens értelmezhet és megjeleníthet. A webalkalmazás-fejlesztés során a HTML-dokumentumokat általában sablonként hozzák létre, és elkülönítve tartják a szerverszkripttől, amely dinamikus adatokat illeszt be a megfelelő sablon előre meghatározott helyére, majd a formázott választ elküldi a kliensnek.

A webes alkalmazások gyakran hasznának statikus és dinamikus erőforrásokat egyaránt. Egy HTML-dokumentum, még ha dinamikusan generálták is, tartalmazhat hivatkozásokat statikus erőforrásokra, például CSS-fájlokra és képekre. Annak bemutatására, hogy az Express hogyan segít az ilyen jellegű igények kezelésében, először egy statikus fájlokat szállító példaszervert állítunk be, majd olyan útvonalakat implementálunk, amelyek strukturált, sablonalapú válaszokat generálnak.

Statikus fájlok

Az első lépés a kiszolgálóként futó JavaScript-fájl létrehozása. Kövessük az előző leckékben tárgyalt mintát egy egyszerű Express alkalmazás létrehozásához: először hozzunk létre egy server nevű könyvtárat, majd telepítsük az alapkomponenseket az npm paranccsal:

$ mkdir server
$ cd server/
$ npm init
$ npm install express

A belépési ponthoz bármilyen fájlnevet használhatunk, de itt az alapértelmezett fájlnevet fogjuk használni: index.js. A következő lista egy alap index.js fájlt mutat, amelyet a szerverünk kezdőpontjaként fogunk használni:

const express = require('express')
const app = express()
const host = "myserver"
const port = 8080

app.listen(port, host, () => {
  console.log(`A szerver kész a http://${host}:${port} címen`)
})

Nem kell explicit kódot írnunk egy statikus fájl elküldéséhez. Az Expressnek van erre a célra egy middleware-je, az express.static. Ha a szervernek statikus fájlokat kell küldenie a kliensnek, csak töltsük be az express.static middleware-t a szkript elején:

app.use(express.static('public'))

A public paraméter azt a mappát jelöli, amely a kliens által lekérhető fájlokat tárolja. A kliensek által kért elérési utak nem tartalmazhatják a public mappát, csak a fájlnevet, vagy a fájl elérési útját a public mappához képest. A public/layout.css fájl lekéréséhez például a kliens a /layout.css könyvtárba küld kérést.

Formázott kimenet

Míg a statikus tartalmak küldése egyszerű, a dinamikusan generált tartalmak nagyon eltérőek lehetnek. A rövid üzeneteket tartalmazó dinamikus válaszok létrehozása megkönnyíti az alkalmazások tesztelését a fejlesztés kezdeti szakaszában. A következő példa egy tesztút, amely csak visszaküld a kliensnek egy üzenetet, amelyet a HTTP POST módszerrel küldött. A válasz csak az üzenet tartalmát ismétli egyszerű szövegként, mindenféle formázás nélkül:

app.post('/echo', (req, res) => {
  res.send(req.body.message)
})

Egy ilyen út nem csak az Express tanulásához jó példa, hanem diagnosztikai célokra is, ahol a res.send() paranccsal küldött nyers válasz is elég. Egy hasznos szervernek azonban képesnek kell lennie összetettebb válaszok előállítására is. Most továbblépünk, hogy ilyen kifinomultabb típusú utakat fejlesszünk.

Az új alkalmazásunk ahelyett, hogy csak az aktuális kérés tartalmát küldené vissza, teljes listát vezet az egyes kliensek által a korábbi kérésekben küldött üzenetekről, és kérésre visszaküldi az egyes kliensek listáját. Lehetőség van az összes üzenetet egyesítő válaszra, de megfelelőbbek lehetnek más formázott kimeneti módok, különösen, ha a válaszok egyre bonyolultabbá válnak.

Az aktuális munkamenet során küldött kliensüzenetek fogadásához és tárolásához először is extra modulokat kell beépítenünk a sütik és a HTTP POST módszerrel küldött adatok kezeléséhez. Az alábbi példaszerver egyetlen célja a POST módszerrel küldött üzenetek naplózása és a korábban küldött üzenetek megjelenítése, amikor a kliens GET kérést küld. Tehát két útvonal van a / útvonalhoz. Az első útvonal a HTTP POST módszerrel, a második pedig a HTTP GET módszerrel küldött kéréseket teljesíti:

const express = require('express')
const app = express()
const host = "myserver"
const port = 8080

app.use(express.static('public'))

const cookieParser = require('cookie-parser')
app.use(cookieParser())

const { v4: uuidv4 } = require('uuid')

app.use(express.urlencoded({ extended: true }))

// Tömb az üzenetek tárolásához
let messages = []

app.post('/', (req, res) => {

	  // Csak JSON-kompatibilis kérések
  if ( req.headers.accept != "application/json" )
  {
    res.sendStatus(404)
    return
  }

  // Süti lokalizálása a kérésben
  let uuid = req.cookies.uuid

  // Ha nincs uuid süti, jöjjön létre egy új
  if ( uuid === undefined )
    uuid = uuidv4()

  // Üzenet hozzáadása az üzenetek tömbhöz
  messages.unshift({uuid: uuid, message: req.body.message})

  // A korábbi üzenetek összegyűjtése az uuid számára
  let user_entries = []
  messages.forEach( (entry) => {
    if ( entry.uuid == req.cookies.uuid )
      user_entries.push(entry.message)
  })

  // A süti lejárati dátumának frissítése
  let expires = new Date(Date.now());
  expires.setDate(expires.getDate() + 30);
  res.cookie('uuid', uuid, { expires: expires })

  // JSON-válasz visszaküldése
  res.json(user_entries)

})

app.get('/', (req, res) => {

	  // Csak JSON-kompatibilis kérések
  if ( req.headers.accept != "application/json" )
  {
    res.sendStatus(404)
    return
  }

  // Süti lokalizálása a kérésben
  let uuid = req.cookies.uuid

  // A kliens saját üzenetei
  let user_entries = []

  // Ha nincs uuid süti, jöjjön létre egy új
  if ( uuid === undefined ){
    uuid = uuidv4()
  }
  else {
    // Üzenetek összegyűjtése az uuid számára
    messages.forEach( (entry) => {
      if ( entry.uuid == req.cookies.uuid )
        user_entries.push(entry.message)
    })
  }

  // A süti lejárati dátumának frissítése
  let expires = new Date(Date.now());
  expires.setDate(expires.getDate() + 30);
  res.cookie('uuid', uuid, { expires: expires })

  // JSON-válasz visszaküldése
  res.json(user_entries)

})

app.listen(port, host, () => {
  console.log(`A szerver kész a http://${host}:${port} címen`)
})

A statikus fájlok konfigurációját felül tartottuk, mert nemsokára hasznos lesz olyan statikus fájlokat biztosítani, mint a layout.css. Az előző fejezetben bemutatott cookie-parser middleware mellett a példa tartalmazza a uuid middlewaret is, amely egy egyedi azonosítószámot generál, amelyet sütiként adunk át minden egyes kliensnek, aki üzenetet küld. Ha még nincsenek telepítve a példaszerver könyvtárába, akkor ezek a modulok az npm install cookie-parser uuid paranccsal telepíthetők.

A messages nevű globális tömb tárolja az összes kliens által küldött üzenetet. A tömb minden egyes eleme egy objektumból áll, amelynek tulajdonságai a uuid és az message.

Ami igazán új ebben a szkriptben, az a res.json() metódus, amelyet a két út végén használunk, hogy JSON-formátumú választ generáljunk a kliens által már elküldött üzeneteket tartalmazó tömbbel:

// JSON-válasz visszaküldése
res.json(user_entries)

A JSON egy egyszerű szöveges formátum, amely lehetővé teszi, hogy egy adathalmazt egyetlen asszociatív struktúrába csoportosítsunk: a tartalom kulcsok és értékek formájában jelenik meg. A JSON különösen akkor hasznos, ha a válaszokat a kliens fogja feldolgozni. Ennek a formátumnak a használatával egy JavaScript-objektum vagy tömb könnyen rekonstruálható a kliensoldalon az eredeti objektum összes tulajdonságával és indexével a szerveren.

Mivel minden egyes üzenetet JSON-ban strukturálunk, elutasítjuk azokat a kéréseket, amelyek nem tartalmazzák az accept fejlécben az application/json kifejezést:

// Csak JSON-kompatibilis kérések
if ( req.headers.accept != "application/json" )
{
  res.sendStatus(404)
  return
}

A sima curl paranccsal egy új üzenet beillesztésére irányuló kérés nem kerül elfogadásra, mivel a curl alapértelmezés szerint nem adja meg az application/json opciót az accept fejlécben:

$ curl http://myserver:8080/ --data message="Az első üzenetem" -c cookies.txt -b cookies.txt
Not Found

A -H "accept: application/json" kapcsoló megváltoztatja a kérés fejlécét, hogy megadja a válasz formátumánt, ami ezúttal a megadott formátumban lesz elfogadva és megválaszolva:

$ curl http://myserver:8080/ --data message="Az első üzenetem" -c cookies.txt -b cookies.txt -H "accept: application/json"
["Az első üzenetem"]

A másik útvonalat használó üzenetek megszerzése hasonló módon történik, de ezúttal a HTTP GET módszerrel:

$ curl http://myserver:8080/ -c cookies.txt -b cookies.txt -H "accept: application/json"
["Másik üzenet","Az első üzenetem"]

Sablonok

Az olyan formátumú válaszok, mint a JSON, kényelmesek a programok közötti kommunikációhoz, de a legtöbb webes alkalmazásszerver fő célja az emberi fogyasztásra szánt HTML-tartalom előállítása. A HTML-kód beágyazása a JavaScript-kódba nem jó ötlet, mert a nyelvek keveredése ugyanabban a fájlban a programot hibaérzékenyebbé teszi, és árt a kód karbantartásának.

Az Express képes együttműködni különböző sablonmotorokkal (template engine), amelyek elkülönítik a HTML-t a dinamikus tartalomhoz; a teljes lista megtalálható a Express sablonmotorok webhelyen. Az egyik legnépszerűbb sablonmotor az Embedded JavaScript (EJS), amely lehetővé teszi, hogy HTML fájlokat hozzunk létre speciális címkékkel a dinamikus tartalom beillesztéséhez.

A többi Express komponenshez hasonlóan az EJS-t is abba a mappába kell telepíteni, ahol a szerver fut:

$ npm install ejs

Ezután az EJS motort alapértelmezett renderelőként kell beállítani a szerverszkriptben (az index.js fájl elején, az útvonaldefiníciók előtt):

app.set('view engine', 'ejs')

A sablonnal generált választ a res.render() függvénnyel küldjük el a kliensnek, amely paraméterként megkapja a sablonfájl nevét és egy olyan objektumot, amely a sablonon belül elérhető értékeket tartalmazza. Az előző példában használt utak átírhatók úgy, hogy a JSON mellett HTML válaszokat is generáljanak:

app.post('/', (req, res) => {

  let uuid = req.cookies.uuid

  if ( uuid === undefined )
    uuid = uuidv4()

  messages.unshift({uuid: uuid, message: req.body.message})

  let user_entries = []
  messages.forEach( (entry) => {
    if ( entry.uuid == req.cookies.uuid )
      user_entries.push(entry.message)
  })

  let expires = new Date(Date.now());
  expires.setDate(expires.getDate() + 30);
  res.cookie('uuid', uuid, { expires: expires })

  if ( req.headers.accept == "application/json" )
    res.json(user_entries)
  else
    res.render('index', {title: "Üzeneteim", messages: user_entries})

})

app.get('/', (req, res) => {

  let uuid = req.cookies.uuid

  let user_entries = []

  if ( uuid === undefined ){
    uuid = uuidv4()
  }
  else {
    messages.forEach( (entry) => {
      if ( entry.uuid == req.cookies.uuid )
        user_entries.push(entry.message)
    })
  }

  let expires = new Date(Date.now());
  expires.setDate(expires.getDate() + 30);
  res.cookie('uuid', uuid, { expires: expires })

  if ( req.headers.accept == "application/json" )
    res.json(user_entries)
  else
    res.render('index', {title: "Üzeneteim", messages: user_entries})

})

Vegyük figyelembe, hogy a válasz formátuma a kérelemben található accept fejléctől függ:

if ( req.headers.accept == "application/json" )
  res.json(user_entries)
else
  res.render('index', {title: "Üzeneteim", messages: user_entries})

A JSON formátumú válasz csak akkor kerül elküldésre, ha a kliens ezt explicit módon kéri. Ellenkező esetben a válasz az index sablonból generálódik. Ugyanaz a user_entries tömb táplálja mind a JSON kimenetet, mind a sablont. Az utóbbi paramétereként használt objektumnak van a title: "Az üzeneteim" tulajdonsága, amely a sablonon belül címként lesz használva.

HTML sablonok

A statikus fájlokhoz hasonlóan a HTML-sablonokat tartalmazó fájlok is a saját mappájukban találhatók meg. Alapértelmezés szerint az EJS feltételezi, hogy a sablonfájlok a views/ könyvtárban vannak. A példában egy index nevű sablont használtunk, így az EJS a views/index.ejs fájlt keresi. A következő felsorolás egy egyszerű views/index.ejs sablon tartalma, amely a példakóddal együtt használható:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title><%= title %></title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="/layout.css">
</head>
<body>

<div id="interface">

<form action="/" method="post">
<p>
  <input type="text" name="message">
  <input type="submit" value="Elküld">
</p>
</form>

<ul>
<% messages.forEach( (message) => { %>
<li><%= message %></li>
<% }) %>
</ul>

</div>

</body>
</html>

Az első speciális EJS tag a <title> elem a <head> szekcióban:

<%= title %>

A feldolgozás során ezen a speciális címke helyébe a res.render() függvény paramétereként átadott objektum title tulajdonságának értéke lép.

A sablon nagy része hagyományos HTML-kódból áll, így a sablon tartalmazza az új üzenetek küldésére szolgáló HTML-űrlapot. A tesztszerver a HTTP GET és POST módszerekre válaszol, ugyanazon a / elérési útvonalon, ezért vannak az űrlap tagben az action="/" és a method="post" attribútumok.

A sablon egyéb részei HTML-kód és EJS-tagek keverékei. Az EJS a sablonon belül speciális célokra szolgáló tagekkel rendelkezik:

<% … %>

Folyamvezérlés beillesztése. Ez a tag közvetlenül nem illeszt be tartalmat, de JavaScript struktúrákkal együtt használható a HTML-szakaszok kiválasztására, megismétlésére vagy elhagyására. Példa egy ciklus indítására: <% messages.forEach( (message) ⇒ { %>

<%# … %>

Meghatároz egy kommentet, amelynek tartalmát a feldolgozás során figyelmen kívül hagyjuk. A HTML-ben írt kommentekkel ellentétben ezek a kommentek nem láthatók a kliens számára.

<%= … %>

Beilleszti a változó escapelt (feloldott) tartalmát. Az ismeretlen tartalmak feloldása fontos, mivel ezzel elkerülhető a JavaScript-kód futtatása, amely kiskapukat nyithat a Cross-Site Scripting (XSS) támadások számára. Példa: <%= title %>

<%- … %>

A változó tartalmának beillesztése escaping nélkül.

A HTML-kód és az EJS-tagek keveredése jól látható abban a kódrészletben, ahol a kliensüzenetek HTML-listaként jelennek meg:

<ul>
<% messages.forEach( (message) => { %>
<li><%= message %></li>
<% }) %>
</ul>

Ebben a kódrészletben az első <% … %> tag egy forEach utasítással kezdődik, ami bejárja a message tömb összes elemét. A <% és a %> delmiterek segítségével kontrollálhatjuk a HTML részleteit. Egy új HTML listaelem, a <li><%= message %></li>, jön létre a messages minden eleméhez. Ezekkel a változtatásokkal a szerver HTML-ben küldi el a választ, amikor egy olyan kérés érkezik, mint az alábbi:

$ curl http://myserver:8080/ --data message="Most" -c cookies.txt -b cookies.txt
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Üzeneteim</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="/layout.css">
</head>
<body>

<div id="interface">

<form action="/" method="post">
<p>
  <input type="text" name="message">
  <input type="submit" value="Elküld">
</p>
</form>

<ul>

<li>Most</li>

<li>HTML-ben</li>

</ul>

</div>

</body>
</html>

A kérések feldolgozására és a válasz megjelenítésére szolgáló kódok szétválasztása tisztábbá teszi az egész kódot, és lehetővé teszi, hogy a csapat az alkalmazásfejlesztést különböző szaktudással rendelkező emberek között ossza fel. Egy webdesigner például a views/ könyvtárban található sablonfájlokra és a kapcsolódó stíluslapokra összpontosíthat, amelyek a példaszerver public/ könyvtárában tárolt statikus fájlokként vannak megadva.

Gyakorló feladatok

  1. Hogyan kell beállítani az express.static állományt, hogy a kliensek az assets könyvtárban lévő fájlokat kérhessék le?

  2. Hogyan azonosítható annak a válasznak a típusa egy Express úton belül, amelyet a kérés fejlécében adunk meg?

  3. A res (válasz) út paraméter melyik módszere generál JSON formátumú választ a content nevű JavaScript tömbből?

Gondolkodtató feladatok

  1. Alapértelmezés szerint az Express sablonfájlok a views könyvtárban vannak. Hogyan lehet ezt a beállítást úgy módosítani, hogy a sablonfájlok a templates könyvtárban legyenek tárolva?

  2. Tegyük fel, hogy a kliens cím nélküli HTML-választ kap (azaz <title></title>). Az EJS-sablon ellenőrzése után a fejlesztő megtalálja a <title><% title %></title> taget a fájl head szekciójában. Mi lehet a probléma legvalószínűbb oka?

  3. Az EJS sabloncímkék segítségével írjunk egy <h2></h2> HTML-címkét a h2 JavaScript-változó tartalmával! Ez a tag csak akkor jelenjen meg, ha a h2 változó nem üres!

Összefoglalás

Ez a lecke az Express.js alapvető módszereit mutatja be a statikus és formázott, de mégis dinamikus válaszok generálásához. A HTTP-szerver beállításához statikus fájlok esetén kevés erőfeszítés szükséges, az EJS sablonozó rendszere pedig egyszerű módot biztosít a dinamikus tartalom HTML-fájlokból történő generálására. Ez a lecke a következő fogalmakat és eljárásokat veszi át:

  • Az express.static használata statikus fájl-válaszokhoz.

  • Hogyan hozzunk létre olyan választ, amely megfelel a kérés fejlécében lévő tartalomtípus mezőnek.

  • JSON-struktúrájú válaszok.

  • EJS-tagek használata HTML-alapú sablonokban.

Válaszok a gyakorló feladatokra

  1. Hogyan kell beállítani az express.static állományt, hogy a kliensek az assets könyvtárban lévő fájlokat kérhessék le?

    Az app.use(express.static('assets')) hívást hozzá kell adni a szerverszkripthez.

  2. Hogyan azonosítható annak a válasznak a típusa egy Express úton belül, amelyet a kérés fejlécében adunk meg?

    A kliens az elfogadható típusokat az accept fejléc mezőben határozza meg, amely a req.headers.accept tulajdonsághoz van rendelve.

  3. A res (válasz) út paraméter melyik módszere generál JSON formátumú választ a content nevű JavaScript tömbből?

    A res.json() metódus: res.json(content).

Válaszok a gondolkodtató feladatokra

  1. Alapértelmezés szerint az Express sablonfájlok a views könyvtárban vannak. Hogyan lehet ezt a beállítást úgy módosítani, hogy a sablonfájlok a templates könyvtárban legyenek tárolva?

    A mappát a kezdeti szkriptbeállításokban az app.set('views', './templates') paranccsal lehet meghatározni.

  2. Tegyük fel, hogy a kliens cím nélküli HTML-választ kap (azaz <title></title>). Az EJS-sablon ellenőrzése után a fejlesztő megtalálja a <title><% title %></title> taget a fájl head szekciójában. Mi lehet a probléma legvalószínűbb oka?

    A <%= %> címkét egy változó tartalmának bezárására kell használni, mint például a <%= title %> esetben.

  3. Az EJS sabloncímkék segítségével írjunk egy <h2></h2> HTML-címkét a h2 JavaScript-változó tartalmával! Ez a tag csak akkor jelenjen meg, ha a h2 változó nem üres!

    <% if ( h2 != "" ) { %>
    <h2><%= h2 %></h2>
    <% } %>

Linux Professional Insitute Inc. Minden jog fenntartva. Látogasson el a tananyagok weboldalára: https://learning.lpi.org
Ez a munka a Creative Commons Nevezd meg! - Ne add el! - Ne változtasd! 4.0 (CC BY-NC-ND 4.0) Nemzetközi lincence alapján van engedélyezve.

Következő lecke

035.3 SQL alapok (035.3 Lecke 1)

Olvassa el a következő leckét

Linux Professional Insitute Inc. Minden jog fenntartva. Látogasson el a tananyagok weboldalára: https://learning.lpi.org
Ez a munka a Creative Commons Nevezd meg! - Ne add el! - Ne változtasd! 4.0 (CC BY-NC-ND 4.0) Nemzetközi lincence alapján van engedélyezve.

Az LPI egy non-profit szervezet.

© 2023 A Linux Professional Institute (LPI) a nyílt forráskóddal foglalkozó szakemberek globális tanúsítási szabványa és karrier-támogató szervezete. Több mint 175 000 kiadott tanúsítvánnyal, ez a világ első és legnagyobb gyártósemleges Linux és nyílt forráskódú tanúsító testülete. Az LPI a világ több mint 180 országban rendelkezik minősített szakemberekkel, számos nyelven készít vizsgasorokat, valamint több száz képzési partnere van..

Célunk, hogy mindenki számára lehetővé tegyünk gazdasági és kreatív lehetőségeket azáltal, hogy a nyílt forráskódú ismeretek és készségek tanúsítását általánosan hozzáférhetővé tesszük.

  • LinkedIn
  • flogo-RGB-HEX-Blk-58 Facebook
  • Twitter
  • Vegye fel velünk a kapcsolatot
  • Adatvédelmi és cookie-irányelvek

Észrevett egy hibát vagy csak segíteni szeretne az oldal fejlesztésében? Kérjük, tudassa velünk.

© 1999–2023 The Linux Professional Institute Inc. Minden jog fenntartva.