package main import ( "encoding/json" "fmt" "html/template" "io" "log" "net/http" "net/url" "strings" "time" gliffy2drawio "gliffy2drawio" ) const ( maxUploadSize = 10 << 20 // 10MB ) var diagramStore = struct { data map[string]string }{ data: make(map[string]string), } var uploadTpl = template.Must(template.New("upload").Parse(` Gliffy β†’ draw.io Converter

Gliffy β†’ draw.io Converter

Upload a .gliffy (zip) or .gon JSON file. Convert to .drawio, or open directly in diagrams.net.

Drag & drop your Gliffy file here
…or choose a file.

API docs: Swagger UI

Source: git.coadcorp.com/nathan/gliffy2drawio

`)) type apiRequest struct { Data string `json:"data"` // raw Gliffy JSON } type apiResponse struct { XML string `json:"xml"` Warn string `json:"warning,omitempty"` } func main() { mux := http.NewServeMux() mux.HandleFunc("/", uploadHandler) mux.HandleFunc("/convert", uploadConvertHandler) mux.HandleFunc("/open", uploadOpenHandler) mux.HandleFunc("/api/convert", apiConvertHandler) mux.HandleFunc("/openapi.json", openAPISpecHandler) mux.HandleFunc("/swagger", swaggerUIHandler) mux.HandleFunc("/diagram", diagramHandler) addr := ":8080" log.Printf("Server listening on %s", addr) log.Fatal(http.ListenAndServe(addr, mux)) } func uploadHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } if err := uploadTpl.Execute(w, nil); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } func uploadConvertHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } log.Printf("[upload] %s %s from %s", r.Method, r.URL.Path, r.RemoteAddr) r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize) if err := r.ParseMultipartForm(maxUploadSize); err != nil { http.Error(w, "failed to parse form: "+err.Error(), http.StatusBadRequest) return } file, header, err := r.FormFile("file") if err != nil { http.Error(w, "missing file: "+err.Error(), http.StatusBadRequest) return } defer file.Close() data, err := io.ReadAll(file) if err != nil { http.Error(w, "failed to read file: "+err.Error(), http.StatusInternalServerError) return } xml, warn, err := convert(string(data)) if err != nil { log.Printf("[upload] conversion failed: %v", err) http.Error(w, "conversion failed: "+err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.drawio"`, safeName(header.Filename))) w.Header().Set("Content-Type", "application/xml") if warn != "" { w.Header().Set("X-Conversion-Warning", warn) } log.Printf("[upload] conversion succeeded, warn: %q, bytes out: %d", warn, len(xml)) _, _ = w.Write([]byte(xml)) } func uploadOpenHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } log.Printf("[open] %s %s from %s", r.Method, r.URL.Path, r.RemoteAddr) r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize) if err := r.ParseMultipartForm(maxUploadSize); err != nil { http.Error(w, "failed to parse form: "+err.Error(), http.StatusBadRequest) return } file, _, err := r.FormFile("file") if err != nil { http.Error(w, "missing file: "+err.Error(), http.StatusBadRequest) return } defer file.Close() data, err := io.ReadAll(file) if err != nil { http.Error(w, "failed to read file: "+err.Error(), http.StatusInternalServerError) return } xml, warn, err := convert(string(data)) if err != nil { log.Printf("[open] conversion failed: %v", err) http.Error(w, "conversion failed: "+err.Error(), http.StatusBadRequest) return } if warn != "" { log.Printf("[open] conversion warning: %s", warn) } id := storeDiagram(xml) targetURL := buildDiagramsNetURL(r, id) log.Printf("[open] redirecting to %s", targetURL) http.Redirect(w, r, targetURL, http.StatusFound) } func apiConvertHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } log.Printf("[api] %s %s from %s", r.Method, r.URL.Path, r.RemoteAddr) defer r.Body.Close() var req apiRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid JSON: "+err.Error(), http.StatusBadRequest) return } xml, warn, err := convert(req.Data) if err != nil { log.Printf("[api] conversion failed: %v", err) http.Error(w, "conversion failed: "+err.Error(), http.StatusBadRequest) return } resp := apiResponse{XML: xml, Warn: warn} w.Header().Set("Content-Type", "application/json") log.Printf("[api] conversion succeeded, warn: %q, bytes out: %d", warn, len(xml)) _ = json.NewEncoder(w).Encode(resp) } func convert(gliffyJSON string) (string, string, error) { log.Printf("[convert] starting, input bytes: %d", len(gliffyJSON)) converter, err := gliffy2drawio.NewGliffyDiagramConverter(gliffyJSON) if err != nil { log.Printf("[convert] failed to initialize converter: %v", err) return "", "", err } xml, err := converter.GraphXML() if err != nil { log.Printf("[convert] failed to generate XML: %v", err) return "", "", err } warn := "" log.Printf("[convert] done, output bytes: %d", len(xml)) return xml, warn, nil } func safeName(name string) string { if name == "" { return "diagram" } dot := strings.LastIndex(name, ".") if dot > 0 { name = name[:dot] } name = strings.ReplaceAll(name, "\"", "") return name } func openAPISpecHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(openAPISpec)) } func diagramHandler(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodOptions { addCORS(w) w.WriteHeader(http.StatusNoContent) return } id := r.URL.Query().Get("id") if id == "" { http.Error(w, "missing id", http.StatusBadRequest) return } xml, ok := diagramStore.data[id] if !ok { http.Error(w, "not found", http.StatusNotFound) return } addCORS(w) w.Header().Set("Content-Type", "application/xml") _, _ = w.Write([]byte(xml)) } func storeDiagram(xml string) string { now := time.Now().UnixNano() id := fmt.Sprintf("diag-%d", now) diagramStore.data[id] = xml return id } func buildDiagramsNetURL(r *http.Request, id string) string { scheme := "http" if r.Header.Get("X-Forwarded-Proto") == "https" || r.TLS != nil { scheme = "https" } base := fmt.Sprintf("%s://%s/diagram?id=%s", scheme, r.Host, url.QueryEscape(id)) // Request AWS library for custom shapes (e.g., aws4) to render correctly. return "https://app.diagrams.net/?splash=0&ui=min&libs=aws4&url=" + url.QueryEscape(base) } func addCORS(w http.ResponseWriter) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type") } func swaggerUIHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } swaggerHTML := ` Gliffy β†’ draw.io API
` w.Header().Set("Content-Type", "text/html; charset=utf-8") _, _ = w.Write([]byte(swaggerHTML)) } var openAPISpec = ` { "openapi": "3.0.0", "info": { "title": "Gliffy to draw.io Converter", "version": "1.0.0" }, "paths": { "/api/convert": { "post": { "summary": "Convert Gliffy JSON to draw.io XML", "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "string", "description": "Raw Gliffy JSON content" } }, "required": ["data"] } } } }, "responses": { "200": { "description": "Conversion succeeded", "content": { "application/json": { "schema": { "type": "object", "properties": { "xml": { "type": "string" }, "warning": { "type": "string" } } } } } }, "400": { "description": "Conversion failed" } } } } } } `