Com pujar i executar scripts Python, des de zero fins a peticions Ajax amb resposta JSON.
TINET permet executar codi Python al servidor web via el protocol CGI (Common Gateway Interface). El flux és senzill:
.py.cgi-bin/ i executa el fitxer Python.Content-Type) i el cos de la resposta.| Paràmetre | Valor | Notes |
|---|---|---|
| Servidor (host) | usuaris.tinet.cat | Tant per FTP com per SFTP |
| Nom d'usuari | el_teu_usuari | El mateix que fas servir al correu TINET |
| Contrasenya | la_teva_contrasenya | La mateixa que el correu |
| Port FTP | 21 | FTP estàndard (no xifrat) |
| Port SFTP | 22 | Recomanat: transferència xifrada |
Un cop connectat, trobaràs aquesta estructura al servidor remot:
La teva URL pública serà https://usuaris.tinet.cat/el_teu_usuari/
FileZilla és el client FTP/SFTP més estès. Pots descarregar-lo gratuïtament a filezilla-project.org.
Protocol: SFTP – SSH File Transfer Protocol Servidor: usuaris.tinet.cat Port: 22 Mode accés: Normal Usuari: el_teu_usuari Contrasenya: la_teva_contrasenya
/html/. Si no ets-hi automàticament, escriu /html al camp "Lloc remot" de la barra superior i prem Intro..html aquí directament: arrossega'ls des del panell esquerre (local) al dret (remot), o clic dret → Puja.cgi-bin si no existeix: clic dret al panell remot → Crea directori → escriu cgi-bin. Entra-hi i puja els fitxers .py.cgi-bin/ → Permisos del fitxer… → escriu 755 al camp numèric i marca "Aplica a tots els subdirectoris i fitxers". Fes el mateix per a cada fitxer .py individualment si cal.
.py han de tenir salts de línia Unix (LF), no Windows (CRLF). Si els has editat amb el Bloc de notes de Windows, és probable que tinguin CRLF i el servidor donarà error 500. Fes servir VS Code, Notepad++ (format → Unix), o qualsevol editor modern configurat per defecte a LF.
Si treballes en un terminal, tens dues opcions ràpides: sftp (interactiu) i scp (còpia directa, com cp però remot).
# Connecta't al servidor sftp el_teu_usuari@usuaris.tinet.cat # Un cop dins (veuràs el prompt "sftp>") cd html # entra a la carpeta web mkdir cgi-bin # crea la carpeta si no existeix put servidor.html # puja el fitxer HTML put servidor_ajax.html cd cgi-bin put consulta.py # puja els scripts Python put consulta_ajax.py # Assigna permisos executables chmod 755 consulta.py chmod 755 consulta_ajax.py chmod 755 . # la carpeta cgi-bin també bye # tanca la sessió
# Puja un fitxer HTML scp servidor.html el_teu_usuari@usuaris.tinet.cat:html/ # Puja un script Python a cgi-bin scp consulta.py el_teu_usuari@usuaris.tinet.cat:html/cgi-bin/ # Puja tota una carpeta local de cop (-r = recursiu) scp -r cgi-bin/ el_teu_usuari@usuaris.tinet.cat:html/ # Assigna permisos via ssh (sense haver d'entrar manualment) ssh el_teu_usuari@usuaris.tinet.cat "chmod 755 html/cgi-bin/*.py html/cgi-bin/"
ssh-keygen i copia la clau pública al servidor amb ssh-copy-id el_teu_usuari@usuaris.tinet.cat. A partir d'aquí, sftp, scp i ssh funcionaran sense contrasenya.
# Instal·la lftp si no el tens sudo apt install lftp # Debian/Ubuntu sudo dnf install lftp # Fedora/RHEL # Connecta i sincronitza la carpeta local "web/" amb /html/ al servidor lftp -u el_teu_usuari sftp://usuaris.tinet.cat mirror -R web/ html/ # -R = puja (reverse mirror) bye
| Element | Permís | Per què |
|---|---|---|
| cgi-bin/ | 755 | Apache ha de poder entrar-hi |
| consulta.py | 755 | El script ha de ser executable |
| la_pagina.html | 644 | Fitxer llegible, no executable |
A FileZilla: clic dret sobre el fitxer → Permisos del fitxer → escriu 755 al camp numèric.
Tots els scripts Python han de començar exactament amb aquesta línia:
#!/usr/bin/env python3
Sense aquesta línia, Apache no sap com executar el fitxer i retornarà un error 500.
El formulari envia les dades al servidor, Python les processa i retorna una pàgina HTML nova. Senzill i directe.
servidor.html<!DOCTYPE html> <html lang="ca"> <head> <meta charset="UTF-8"> <title>Demo CGI - TINET</title> </head> <body> <h1>Exemple CGI clàssic</h1> <!-- Exemple: hora del servidor --> <form action="cgi-bin/consulta.py" method="get"> <input type="hidden" name="accio" value="hora"> <button type="submit">Quina hora és al servidor?</button> </form> <!-- Exemple: salutació personalitzada --> <form action="cgi-bin/consulta.py" method="get"> <input type="hidden" name="accio" value="saluda"> <label>Nom: <input type="text" name="nom"></label> <button type="submit">Envia</button> </form> </body></html>
cgi-bin/consulta.py#!/usr/bin/env python3 import cgi, cgitb, datetime, html cgitb.enable() # mostra errors al navegador (treure en producció) form = cgi.FieldStorage() accio = form.getvalue('accio', 'desconeguda') # Capçalera HTTP — SEMPRE la primera cosa que imprimim print("Content-Type: text/html; charset=utf-8") print() # línia en blanc obligatòria print("""<!DOCTYPE html><html lang="ca"><head> <meta charset="UTF-8"><title>Resposta</title> </head><body>""") if accio == 'hora': ara = datetime.datetime.now() print(f"<h2>Hora del servidor: {ara.strftime('%H:%M:%S')}</h2>") print(f"<p>Data: {ara.strftime('%A, %d de %B de %Y')}</p>") elif accio == 'saluda': nom = html.escape(form.getvalue('nom', 'desconegut')) print(f"<h2>Hola, {nom}!</h2>") else: print("<p>Acció desconeguda.</p>") print("</body></html>")
action="cgi-bin/consulta.py") és relativa al fitxer HTML. Si el teu HTML és a /html/servidor.html, el script ha d'estar a /html/cgi-bin/consulta.py.
La pàgina no es recarrega mai. JavaScript fa la petició, Python retorna JSON, i el DOM s'actualitza al moment. Exactament el flux que necessites.
Content-Type: application/json en lloc de text/html, i que imprimim JSON en lloc d'HTML.
servidor_ajax.html<!DOCTYPE html> <html lang="ca"> <head> <meta charset="UTF-8"> <title>Demo Ajax + CGI - TINET</title> </head> <body> <h1>Exemple Ajax — la pàgina no es recarrega</h1> <!-- Exemple: hora del servidor sense recàrrega --> <button onclick="demanarHora()">Quina hora és al servidor?</button> <div id="resultat-hora"></div> <!-- Exemple: salutació sense recàrrega --> <input type="text" id="nom" placeholder="El teu nom"> <button onclick="demanarSalutacio()">Saluda'm</button> <div id="resultat-saluda"></div> <script> // Funció genèrica: crida el CGI i actualitza un div amb el resultat function cridar(params, idDiv, callback) { const url = 'cgi-bin/consulta_ajax.py?' + new URLSearchParams(params); fetch(url) .then(r => r.json()) .then(data => { document.getElementById(idDiv).innerHTML = callback(data); }) .catch(() => { document.getElementById(idDiv).innerHTML = '<strong style="color:red">Error de connexió</strong>'; }); } function demanarHora() { cridar({ accio: 'hora' }, 'resultat-hora', data => `Hora: <strong>${data.hora}</strong> — ${data.data}` ); } function demanarSalutacio() { const nom = document.getElementById('nom').value; cridar({ accio: 'saluda', nom }, 'resultat-saluda', data => `<strong>${data.missatge}</strong><br>${data.extra}` ); } </script> </body></html>
cgi-bin/consulta_ajax.py#!/usr/bin/env python3 import cgi, cgitb, datetime, html, json cgitb.enable() form = cgi.FieldStorage() accio = form.getvalue('accio', 'desconeguda') # Ara retornem JSON en lloc d'HTML print("Content-Type: application/json; charset=utf-8") print() # línia en blanc obligatòria if accio == 'hora': ara = datetime.datetime.now() resposta = { "hora": ara.strftime('%H:%M:%S'), "data": ara.strftime('%A, %d de %B de %Y'), "timestamp": int(ara.timestamp()) } elif accio == 'saluda': nom = html.escape(form.getvalue('nom', 'desconegut')) edat = form.getvalue('edat', '') extra = f"D'aquí a 10 anys en tindràs {int(edat) + 10}." if edat else '' resposta = { "missatge": f"Hola, {nom}! Resposta generada per Python al servidor TINET.", "extra": extra } elif accio == 'suma': try: a = float(form.getvalue('a', 0)) b = float(form.getvalue('b', 0)) resposta = { "a": a, "b": b, "suma": a + b, "producte": a * b, "diferencia": a - b, "divisio": round(a / b, 4) if b != 0 else "∞" } except (ValueError, TypeError): resposta = {"error": "Els valors han de ser números"} else: resposta = {"error": f"Acció desconeguda: {html.escape(accio)}"} print(json.dumps(resposta, ensure_ascii=False))
Content-Type: application/json i que s'imprimeix json.dumps(resposta) en comptes d'HTML. Tot el mecanisme CGI és idèntic.
Pots ampliar el script afegint nous blocs elif. Per exemple, per llegir un fitxer de dades:
elif accio == 'el_meu_calcul': valor = form.getvalue('parametre', '') # → fes aquí la teva lògica Python resultat = valor.upper() # exemple trivial resposta = { "ok": True, "resultat": resultat }
I al JavaScript de la pàgina HTML, crida-ho amb:
cridar( { accio: 'el_meu_calcul', parametre: 'hola' }, 'id-del-div-resultat', data => data.ok ? `Resultat: ${data.resultat}` : 'Error' );
Pots provar directament els dos exemples complets al servidor TINET:
Usa el codi de les seccions 4 i 5 com a base. Modifica els blocs elif del script Python per afegir la teva pròpia lògica.
| Error | Causa més probable | Solució |
|---|---|---|
| 500 Internal Server Error | Script no executable, shebang incorrecte, o error de sintaxi Python | Comprova permisos (755), shebang (#!/usr/bin/env python3), i que el fitxer tingui salts de línia Unix (LF) |
| 403 Forbidden | La carpeta cgi-bin/ no existeix o no té permisos adequats |
Crea la carpeta i assigna-li permisos 755 |
| 404 Not Found | Ruta incorrecta a l'atribut action del formulari o a la URL del fetch() |
Verifica que la ruta sigui relativa: cgi-bin/consulta.py (sense / inicial) |
| JSON parse error al navegador | El script imprimeix text o HTML abans de la capçalera JSON, o la capçalera és incorrecta | Assegura't que la primera impressió és exactament Content-Type: application/json; charset=utf-8 seguit d'una línia en blanc |
| Caràcters estranys (’ etc.) | Problema d'encodatge: el fitxer no s'ha desat en UTF-8 | Desa el script i l'HTML sempre en UTF-8 sense BOM |
cgitb.enable() al principi del script fa que els errors Python surtin directament al navegador en lloc d'un genèric 500. Recorda treure'l (o desactivar-lo) quan el codi passi a producció.