{"id":39333920,"url":"https://github.com/smhanov/zwibserve","last_synced_at":"2026-01-18T02:06:18.608Z","repository":{"id":47959257,"uuid":"248027472","full_name":"smhanov/zwibserve","owner":"smhanov","description":"Collaboration service for Shared Zwibbler Whiteboards","archived":false,"fork":false,"pushed_at":"2025-05-08T21:20:29.000Z","size":146,"stargazers_count":7,"open_issues_count":2,"forks_count":7,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-08T22:27:38.516Z","etag":null,"topics":["collaboration-server","crdt","drawing-on-canvas","operational-transform","whiteboard"],"latest_commit_sha":null,"homepage":"https://zwibbler.com","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/smhanov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-03-17T17:06:58.000Z","updated_at":"2025-05-08T21:20:33.000Z","dependencies_parsed_at":"2024-05-02T21:41:19.338Z","dependency_job_id":"e673bdf8-f895-4dd6-a17a-dbd300e5162c","html_url":"https://github.com/smhanov/zwibserve","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/smhanov/zwibserve","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smhanov%2Fzwibserve","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smhanov%2Fzwibserve/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smhanov%2Fzwibserve/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smhanov%2Fzwibserve/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/smhanov","download_url":"https://codeload.github.com/smhanov/zwibserve/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smhanov%2Fzwibserve/sbom","scorecard":{"id":833307,"data":{"date":"2025-08-11","repo":{"name":"github.com/smhanov/zwibserve","commit":"496ba7063aa7dad016494d28fc20efb32910e567"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.8,"checks":[{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 1/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 1 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":8,"reason":"2 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GO-2025-3553 / GHSA-mh63-6h87-95cp","Warn: Project is vulnerable to: GO-2024-3250 / GHSA-29wx-vh33-7x7r"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-23T18:18:59.092Z","repository_id":47959257,"created_at":"2025-08-23T18:18:59.092Z","updated_at":"2025-08-23T18:18:59.092Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28526561,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-18T00:39:45.795Z","status":"online","status_checked_at":"2026-01-18T02:00:07.578Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["collaboration-server","crdt","drawing-on-canvas","operational-transform","whiteboard"],"created_at":"2026-01-18T02:06:14.810Z","updated_at":"2026-01-18T02:06:18.602Z","avatar_url":"https://github.com/smhanov.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# zwibserve [![GoDoc](https://godoc.org/github.com/smhanov/zwibserve?status.svg)](https://godoc.org/github.com/smhanov/zwibserve)\n\nPackage zwibserve is an example collaboration server for zwibbler.com. It lets users work on the same document together. The server portion is responsible only for storing the changes and broadcasting them to all connected clients. \n\nThe collaboration works similar to source control. When I try to submit my changes, and you have submitted yours first, then my changes are rejected by the server. I must then modify my changes so they no longer conflict with yours, and try to submit them again.\n\nThe protocol is described in [Zwibbler Collaboration Server Protocol V3](https://docs.google.com/document/d/1l2BsVVsP6mD_BuzErIdD8ig1ICur6D3V0vrYQVWJTDQ/edit?usp=sharing). There are additional methods that add security and the ability to delete documents described in [Zwibbler Collaboration Server Management API](https://docs.google.com/document/d/1vdUUEooti4F5Ob9rca2DVoOJOxyO2uftaCOUzKdXb4M/edit?usp=sharing).\n\n[Test your server online](https://zwibbler.com/collaboration/testing.html).\n\n## What do I need?\n* Linux or Windows server. [.rpm, .deb, and .exe](https://github.com/smhanov/zwibbler-service/releases) installers are provided. \n* Security certificates are highly recommended. Your server needs to be running on HTTPS if your web page containing the drawing tool is also served over HTTPS, otherwise the browser will not allow it to connect. \n\n## Quick install\n\nUse the .deb or .rpm packages from https://github.com/smhanov/zwibbler-service/releases . This will install a system service that automatically restarts if interrupted.\nAfter installation, it will be running on port 3000 as non-https. You can check this by going to http://yourserver.com:3000 in a web browser. You should receive a message that says it is working.\n\nThe next step is to enable HTTPS using your certificate and private key file. You need HTTPS because if your web site is served using HTTPS, it will not be able to contact the collaboration server unless it is also HTTPS.\n\nEdit /etc/zwibbler.conf and change the port to the HTTPS port 443:\n\n    ServerBindAddress=0.0.0.0\n    ServerPort=443\n    CertFile=\n    KeyFile=\n    Compression=\n\nChange CertFile and KeyFile to be the path to your SSL certificate information on the system. CertFile is your certificate, and KeyFile is your private key. You can leave the other settings as they are. Specifying Compression=0 will disable socket compression. Otherwise, it will be enabled by default.\n\nNext, restart the service using\n\n    systemctl restart zwibbler\n\nYou can view the logs using\n\n    sudo tail -f /var/log/zwibbler/zwibbler.log\n\nYou should now be able to test using https://zwibbler.com/collaboration and entering wss://yourserver/socket in the URL with no port.\n\n### Using an nginx proxy\nThe method above will dedicate your server to running only Zwibbler, since it takes over the HTTPS port 443. If you want to run other things on the same machine, you will likely be using the nginx web server. You can run zwibbler on a different port (eg, 3000) and configure nginx to forward requests from a certain URL to zwibbler.\n\nIn this case, you can leave zwibbler.conf at the default, with blank CertFile and KeyFile.  They will not be necessary since nginx will handle the security.\n\n    ServerBindAddress=0.0.0.0\n    ServerPort=3000\n    CertFile=\n    KeyFile=\n\nIn your nginx configuration, include this in your server {} block. This will redirect anything on https://yourserver/socket to the zwibbler service running on port 3000.\n\n    location /socket {\n        proxy_pass http://localhost:3000;\n        proxy_http_version 1.1;\n        proxy_set_header Upgrade $http_upgrade;\n        proxy_set_header Connection \"Upgrade\";\n    }\n\n### Using an Apache proxy\nIf you are running the Apache web server and have existing services running on it, you will need to configure it to forward requests for the socket to the Zwibbler collaboration service.\n\nWhen using a proxy, leave the CertFile/KeyFile blank and keep the Zwibbler service on a different port than 3000. In this case, Apache will handle the security. Example /etc/zwibbler.conf file:\n\n    ServerBindAddress=0.0.0.0\n    ServerPort=3000\n    CertFile=\n    KeyFile=\n\nIf you are running CentOS, you may need to install the Apache proxy module, and configure security to allow Apache to make connections. If the second line fails because you do not have SELinux installed, that is OK.\n\n    sudo yum install mod_proxy\n    sudo /usr/sbin/setsebool -P httpd_can_network_connect 1\n\nFind the virtual host configuration for your web site. It may be in /etc/httpd/conf/httpd.conf or in a file under /etc/httpd/conf.d/. Add the SSLProxyEngine and \u003cLocation\u003e\u003c/Location\u003e lines to your virtual host configuration for your web site. It says to forward any request to /socket to our collaboration service running on port 3000. Here is an example.\n\n\t\u003cVirtualHost *:443\u003e\n\t    SSLEngine on \n\t    SSLProxyEngine On\n\t    ServerName www.example.com\n\t    DocumentRoot \"/var/www/html\"\n\t    SSLCertificateFile /path/to/.crt\n\t    SSLCertificateKeyFile /path/to/.key\n\t    \n\t    # ADD THESE LINES\n\t    SSLProxyEngine on \n\t    \u003cLocation \"/socket\"\u003e\n\t\tProxyPass ws://localhost:3000/socket\n\t\tProxyPassReverse ws://localhost:3000/socket\n\t    \u003c/Location\u003e\n\t\u003c/VirtualHost\u003e\n\n### Using Windows Server\nAfter installing the [setup file](https://github.com/smhanov/zwibbler-service/releases), follow the instructions to [redirect traffic from the /socket url to your server.](https://docs.google.com/document/d/13pb4Accpa1B62gLBcwsWJDSmUND14qjXVIdJwnYc4tg/edit?usp=sharing)\n\nOn Windows, the zwibbler.log, zwibbler.conf, and zwibbler.db files are all located in C:\\zwibbler, and they will not be removed if you uninstall the server.\n\n## Testing if it is working\n\nOnce you have the server running, test if it is working by going to https://yourserver.com/socket in your web browser. You should see a message like this:\n\n    Zwibbler collaboration Server is running.\n    \nAfter that, go to https://zwibbler.com/collaboration/testing.html and enter the your server wss:// url in the box (eg, wss://yourserver.com/socket) and click on \"Run Tests\". You should see the tests all pass and turn green.\n\n### Where is the data? What if it fails?\n\n\nIf the collaboration server restarts, clients will gracefully reconnect without the user noticing anything wrong and any active sessions are preserved.\n\nIn the basic version, each document must be hosted on one server, although a shared database can be used for higher reliability. All users for a particular whiteboard must connect to the same server. You can manage this by sharding the users based on their session identifier.\n\nIf more redundancy is required, or for complex load balancing, then a special, non-open source version is available at extra cost that allows any user to connect to any of the servers, regardless of which whiteboard they are connecting to. See [High Availablility Extensions for Zwibbler Collaboration Server](https://docs.google.com/document/d/1tvMS_eh-uvXZryIxT5naLZmXjk86CzCLsL9E_So4MAE/edit?usp=sharing).\n\nThere are two options for data storage.\n#### Sqlite (default)\nThe data is stored in an SQLITE database in /var/lib/zwibbler/zwibbler.db. The collaboration server is designed to store data only while a session is active. Long term storage should use [ctx.save()](https://zwibbler.com/docs/#save) and store the data using other means. Sessions never expire by default, but you can [add expiration](#document-lifetime) by editing the zwibbler.conf file.\n\n#### Redis\nRedis must be used if using more than one server instance. To use redis, add these lines to the zwibbler.conf file:\n\n    # Change this to redis\n    Database=redis\n\n    # Default DbServer for Redis: localhost:6379\n    DbServer=\n    DbPassword=\n\nThe DbUser field is unnecessary for Redis.\n    \n#### MariaDB, PostgreSQL, and MySQL\nYou can use another relational database for the collaboration server. To use MariaDB, PostgreSQL, or MySQL, change the Database config:\n\n    Database=mariadb\n\nThen insert the DB settings:\n\n    # Default DbServer for MariaDB and MySQL: localhost:3306\n    # Default DbServer for Redis: localhost:6379\n    DbServer=\n    DbPassword=\n    DbUser=\n\n## Advanced options\n\n### Document lifetime\nBy default, documents never expire. You can set an expiration time for documents, so they will automatically be deleted when they have not been accessed for a certain number of seconds.\n\n    # The number of seconds after a document is last accessed to purge unused \n    # documents. Set to the special value \"never\" to disable. \n    Expiration=never\n\n\n### Security\nThe [Zwibbler Collaboration Server Management API](https://docs.google.com/document/d/1vdUUEooti4F5Ob9rca2DVoOJOxyO2uftaCOUzKdXb4M/edit?usp=sharing) adds additional security, so that a skilled student hacker will be unable to alter the Javascript and write to a teacher's whiteboard unless given permission to do so. In this case, you must configure a username and password, and configure your own server software make a request to add a token with permissions before each persion connects to a session. That way, participants  connect using a token instead of a session identifier, and the permissions are enforced by the collaboration server instead of the client browser. Any management requests are authenticated using HTTP Basic Authentication with the given username and password.\n\n    # If set, the management API is enabled to allow deleting and dumping documents.\n    SecretUser=\n    SecretPassword=\n\n    # If set, this webhook will be called when all users have left a session.\n    # See the API documents on Google Drive for details.\n    Webhook=\n\n\n### JWT (Javascript Web Tokens)\nIf desired, the server can be configured to only accept session identifiers contained inside of a JWT. The JWT also contains permission information, but are signed using a preconfigured key. That way, only authorized individuals will be able to write to a whiteboard. Using JWT means that the tokens do not need to be registered in advance with the collaboration server. The format of the tokens is described in [Zwibbler Collaboration Server Management API](https://docs.google.com/document/d/1vdUUEooti4F5Ob9rca2DVoOJOxyO2uftaCOUzKdXb4M/edit#heading=h.wrucymxrj81i)\n\n    # If set, only JWT tokens will be accepted as document identifiers from the client.\n    # The HMAC-SHA256 signature method is used.\n    JWTKey=\n    JWTKeyIsBase64=False\n\n    \n### Increasing maximum number of connections\nTo support more than 1024 connections on Linux, you will have to increase your system limit on the number of file handles. This is often done by adding these lines to /etc/security/limits.conf:\n\n    * hard nofiles 100000\n    * soft nofiles 100000\n    \nTo tell Zwibbler to take advantage of these limits, you set MaxFiles to the same value in /etc/zwibbler.conf:\n    \n    # Attempt to set the open file limit of the operating system to this value.\n    # This must be less than or equal to the hard limit of the operating system.\n     MaxFiles=100000\n     \n## Load testing\nThe current load testing results are available in the [Zwibbler Collaboration Server Load Testing](https://docs.google.com/document/d/1P6wzmka-C3ZbJXgfFGjYgR1v4be3GkLw6tclpwzZn5s/edit?usp=sharing) guide. A single server can support many thousands of connections, even when using the built-in SQLITE database.\n\nThe collaboration server has command line options that enable it to perform load testing on a server on another machine. It does this by simulating a number of participants viewing and writing to a single whiteboard. These are termed students and teachers.\n\n| Command | Description |\n| --- | --- |\n| --test \u003csocket url\u003e | Test the server at the given websocket url (eg. ws://yourserver:3000/socket) |\n| --docid \u003cstring\u003e | The name of the whiteboard all participants connect to |\n| --teachers \u003cnumber\u003e | The number of teachers to simulate. |\n| --students \u003cnumber\u003e | The number of students to simulate. |\n| --verbose | Show all actions from every student and teacher |\n\nAll initial connections will be made over a few seconds, to avoid a long backlog. A simulated student simply connects to the whiteboard and receives the initial contents as well as updates from it. A teacher will connect, and continuously update the whiteboard with new changes as well as receiving updates from other teachers. The teachers make changes at random intervals that average to one second. If there are multiple teachers, the changes may conflict and have to be resent. The test continues until stopped by the user using CTRL-C.\n\nDuring the test you will see statistics about the test, including the screen-to-screen time. This is the amount of time between when a teacher makes a change to when a student sees that change on his whiteboard.\n\n     Connections=51 docLength=149360 Screen-to-screen time avg=71ms min=19ms max=958ms\n\nThe changes are nonsensical data, not real whiteboard commands, so the real zwibbler will be unable to connect to the document used.\n\n## Using it from a go project\nThis is a go package. To run it, you will create a main program like this:\n\n### Step 1: Get the package\nRun `go get github.com/smhanov/zwibserve` to get `go` to install the package in its cache.\n\n### Step 2: Create the main.go file\nCreate a main.go file:\n\n```go\npackage main\n\nimport (\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/smhanov/zwibserve\"\n)\n\nfunc main() {\n\thttp.Handle(\"/socket\", zwibserve.NewHandler(zwibserve.NewSQLITEDB(\"zwibbler.db\")))\n\tlog.Printf(\"Running...\")\n\thttp.ListenAndServe(\":3000\", http.DefaultServeMux)\n}\n\n```\n\n### Step 3: Build and run\nRun `go build` and the server will be compiled as `main`. It will run on port 3000 by default but you can change this in the main() function above.\n\n## Architecture\nArchitecturally, It uses gorilla websockets and follows closely the [hub and client example](https://github.com/gorilla/websocket/tree/master/examples/chat)\n\nA hub goroutine is responsible for keeping a collection of document IDs. Each\ndocument ID has a list of clients connected to it.\n\nClients each run one goroutine for receiving messages, and one goroutine for sending.\n\nThe DocumentDB, which you can implement, actually stores the contents of the documents.\nBefore appending to a document, it must atomically check if the length the client has given matches\nthe actual length of the document.\n\nThe server is meant to only store documents during the time that multiple people are working on them. You should have a more permanent solution to store them for saving / opening.\n\n## Source tree method\n\nUsing the project as a module is the recommended way. You should not need to make modifications to the zwibserve package.  However, if you want to make modifications to zwibserve in your project, you can directly include the files in your project.\n\n### Step 1: Check out the files\n\n```bash\nmkdir myproject\ncd myproject\ngit clone https://github.com/smhanov/zwibserve.git\n```\n\nNow you have your project folder and zwibserve/ inside of it.\n\n### Step 2: Create go.mod file\nNow, create a go.mod file which tells go where to find the zwibserve package.\n```\nmodule example.com/server\n\ngo 1.14\n\nrequire (\n\tzwibserve v0.0.0\n)\n\nreplace zwibserve v0.0.0 =\u003e ./zwibserve\n```\n\n### Step 3: Update main.go to refer to the local package\nCreate the main.go file from above, but remove github.com from the package import.\n\n```go\npackage main\n\nimport (\n\t\"log\"\n\t\"net/http\"\n\n\t\"zwibserve\" // \u003c-- Was github.com/smhanov/zwibserve\n)\n\nfunc main() {\n\thttp.Handle(\"/socket\", zwibserve.NewHandler(zwibserve.NewSQLITEDB(\"zwibbler.db\")))\n\tlog.Printf(\"Running...\")\n\thttp.ListenAndServe(\":3000\", http.DefaultServeMux)\n}\n```\n\n### Step 4: Build and run\n\nRun `go build` to build the project. Run `./server` to start it.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmhanov%2Fzwibserve","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsmhanov%2Fzwibserve","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmhanov%2Fzwibserve/lists"}