Adicionar primeiro

This commit is contained in:
2025-06-06 21:17:25 +01:00
parent c188084ba4
commit 282e7f517b
841 changed files with 199592 additions and 1 deletions

View File

@@ -0,0 +1,6 @@
BasedOnStyle: Google
AllowShortFunctionsOnASingleLine: false
SpaceAfterCStyleCast: true
PointerBindsToType: false
DerivePointerBinding: false
IncludeBlocks: Preserve

View File

@@ -0,0 +1,16 @@
Copyright (c) 2004-2013 Sergey Lyubka
Copyright (c) 2013-2021 Cesanta Software Limited
All rights reserved
This software is dual-licensed: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation. For the terms of this
license, see <http://www.gnu.org/licenses/>.
You are free to use this software under the terms of the GNU General
Public License, but WITHOUT ANY WARRANTY; without even the implied
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
Alternatively, you can license this software under a commercial
license, as set out in <https://mongoose.ws/licensing/>.

View File

@@ -0,0 +1,73 @@
# Mongoose - Embedded Web Server / Embedded Networking Library
[![License: GPLv2/Commercial](https://img.shields.io/badge/License-GPLv2%20or%20Commercial-green.svg)](https://opensource.org/licenses/gpl-2.0.php)
[![Build Status]( https://github.com/cesanta/mongoose/workflows/build/badge.svg)](https://github.com/cesanta/mongoose/actions)
[![Code Coverage](https://codecov.io/gh/cesanta/mongoose/branch/master/graph/badge.svg)](https://codecov.io/gh/cesanta/mongoose)
[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/mongoose.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:mongoose)
Mongoose is a networking library for C/C++. It implements event-driven
non-blocking APIs for TCP, UDP, HTTP, WebSocket, MQTT. It is designed for
connecting devices and bringing them online. On the market since 2004, used by
vast number of open source and commercial products - it even runs on the
International Space Station! Mongoose makes embedded network programming fast,
robust, and easy. Features include:
- Cross-platform: works on Linux/UNIX, MacOS, Windows, Android, FreeRTOS, etc.
- Supported embedded architectures: ESP32, NRF52, STM32, NXP, and more
- Built-in protocols: plain TCP/UDP, HTTP, MQTT, Websocket
- SSL/TLS support: mbedTLS, OpenSSL or custom (via API)
- Asynchronous DNS resolver
- Tiny static and run-time footprint
- Source code is both ISO C and ISO C++ compliant
- Works with any network stack with socket API, like LwIP or FreeRTOS-Plus-TCP
- Very easy to integrate: just copy `mongoose.c` and `mongoose.h` files to your build tree
- Detailed [documentation](https://mongoose.ws/documentation/) and
[tutorials](https://mongoose.ws/tutorials/)
# Commercial use
- Mongoose is used by hundreds of businesses, from Fortune500 giants like
Siemens, Schneider Electric, Broadcom, Bosch, Google, Samsung, Qualcomm, Caterpillar to the small businesses
- Used to solve a wide range of business needs, like implementing Web UI
interface on devices, RESTful API services, telemetry data exchange, remote
control for a product, remote software updates, remote monitoring, and others
- Deployed to hundreds of millions devices in production environment worldwide
- See [Case Studies](https://mongoose.ws/case-studies/) from our respected
customers like [Schneider Electric](https://mongoose.ws/case-studies/schneider-electric/) (industrial automation), [Broadcom](https://mongoose.ws/case-studies/broadcom/) (semiconductors), [Pilz](https://mongoose.ws/case-studies/pilz/) (industrial automation), and others
- See [Testimonials](https://mongoose.ws/testimonials/) from engineers that integrated Mongoose in their commercial products
- We provide [Evaluation and Commercial licensing](https://mongoose.ws/licensing/), [support](https://mongoose.ws/support/), consultancy and integration
assistance - don't hesitate to [contact us](https://mongoose.ws/contact/)
# Security
We take security seriously:
1. Mongoose repository runs a
[continuous integration test powered by GitHub](https://github.com/cesanta/mongoose/actions),
which runs through hundreds of unit tests on every commit to the repository.
Our [unit tests](https://github.com/cesanta/mongoose/tree/master/test)
are built with modern address sanitizer technologies, which help to find
security vulnerabilities early
2. Mongoose repository is integrated into Google's
[oss-fuzz continuous fuzzer](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:mongoose)
which scans for potential vulnerabilities continuously
3. We receive periodic vulnerability reports from the independent security
groups like
[Cisco Talos](https://www.cisco.com/c/en/us/products/security/talos.html),
[Microsoft Security Response Center](https://www.microsoft.com/en-us/msrc),
[MITRE Corporation](https://www.mitre.org/),
[Compass Security](https://www.compass-security.com/en/) and others.
In case of the vulnerability found, we act according to the industry best
practice: hold on to the publication, fix the software and notify all our
customers that have an appropriate subscription
4. Some of our customers (for example NASA)
have specific security requirements and run independent security audits,
of which we get notified and in case of any issue, act similar to (3).
# Contributions
Contributions are welcome! Please follow the guidelines below:
- Sign [Cesanta CLA](https://cesanta.com/cla.html) and send GitHub pull request
- Make sure that PRs have only one commit, and deal with one issue only

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1034 239" style="enable-background:new 0 0 1034 239;" xml:space="preserve">
<style type="text/css">
.st0{fill:#576174;}
.st1{fill:#FFFFFF;}
.st2{font-family:'Courier-Bold';}
.st3{font-size:16.7604px;}
.st4{fill:#00983A;}
.st5{fill:none;stroke:#45CFFF;stroke-width:2.7934;stroke-linecap:round;stroke-linejoin:round;}
.st6{fill:#45CFFF;}
.st7{fill:none;stroke:#A000FF;stroke-width:2.7934;stroke-linecap:round;stroke-linejoin:round;}
.st8{fill:#A000FF;}
.st9{fill:none;stroke:#FFDE00;stroke-width:2.793;stroke-linecap:round;stroke-linejoin:round;}
.st10{fill:#FFDE00;}
.st11{fill:none;stroke:#EA5B0C;stroke-width:2.793;stroke-linecap:round;stroke-linejoin:round;}
.st12{fill:#EA5B0C;}
</style>
<g>
<path class="st0" d="M485.3,224.22H8.5c-4.7,0-8.5-3.8-8.5-8.5V27.22c0-4.7,3.8-8.5,8.5-8.5h476.8c4.7,0,8.5,3.8,8.5,8.5v188.5
C493.8,220.32,490,224.22,485.3,224.22z"/>
<g>
<g>
<g>
<text transform="matrix(1 0 0 1 21.294 60.1267)" class="st1 st2 st3">struct mg_str k,v,s = mg_str(”a=333, b=777”);</text>
</g>
</g>
<g>
<g>
<text transform="matrix(1 0 0 1 21.294 136.2499)" class="st1 st2 st3">mg_commalist(&amp;s, &amp;k, &amp;v); </text>
</g>
</g>
<g>
<g>
<text transform="matrix(1 0 0 1 21.294 197.1485)" class="st1 st2 st3">mg_commalist(&amp;s, &amp;k, &amp;v); </text>
</g>
</g>
<g>
<g>
<text transform="matrix(1 0 0 1 291.5096 140.18)" class="st4 st2 st3">// 1st call</text>
<text transform="matrix(1 0 0 1 291.5096 199.8101)" class="st4 st2 st3">// 2nd call</text>
</g>
</g>
</g>
<g>
<polyline class="st5" points="336.4,64.52 336.4,68.52 317.8,68.52 317.8,64.52 "/>
<g>
<path class="st5" d="M122.3,59.62"/>
<g>
<polyline class="st5" points="327.5,76.82 327.6,94.02 203.3,94.02 203.3,116.12 "/>
<path class="st6" d="M331,77.62c0,1.8-1.4,3.2-3.2,3.3c-1.8,0.1-3.2-1.4-3.3-3.2c0-1.8,1.4-3.2,3.2-3.3l0,0
C329.5,74.42,331,75.92,331,77.62z"/>
</g>
</g>
<path class="st6" d="M203.2,114.92c-1.8,0-3.2,1.3-3.3,3s1.4,3,3.2,3s3.2-1.3,3.3-3l0,0C206.5,116.32,205,114.92,203.2,114.92z"/>
</g>
<g>
<polyline class="st7" points="407.4,64.52 407.4,68.52 388.8,68.52 388.8,64.52 "/>
<g>
<path class="st5" d="M193.4,59.62"/>
<g>
<polyline class="st7" points="398.6,76.82 398.6,149.02 203.4,149.02 203.4,177.12 "/>
<path class="st8" d="M402,77.62c0,1.8-1.4,3.2-3.2,3.3c-1.8,0.1-3.2-1.4-3.3-3.2c0-1.8,1.4-3.2,3.2-3.3l0,0
C400.6,74.42,402,75.92,402,77.62z"/>
</g>
</g>
<path class="st8" d="M203.2,175.42c-1.8,0-3.2,1.3-3.3,3s1.4,3,3.2,3s3.2-1.3,3.3-3l0,0C206.5,176.82,205,175.42,203.2,175.42z"/>
</g>
<g>
<polyline class="st9" points="440.8,64.52 440.8,68.52 414.2,68.52 414.2,64.52 "/>
<g>
<path class="st5" d="M218.8,59.62"/>
<g>
<polyline class="st9" points="428,76.82 428,163.02 245.7,163.02 245.7,176.12 "/>
<path class="st10" d="M431.4,77.62c0,1.8-1.4,3.2-3.2,3.3c-1.8,0.1-3.2-1.4-3.3-3.2c0-1.8,1.4-3.2,3.2-3.3l0,0
C430,74.42,431.4,75.92,431.4,77.62z"/>
</g>
</g>
<path class="st10" d="M245.4,175.42c-1.8,0-3.2,1.3-3.3,3s1.4,3,3.2,3s3.2-1.3,3.3-3l0,0C248.7,176.82,247.2,175.42,245.4,175.42z
"/>
</g>
<g>
<polyline class="st11" points="372.8,64.52 372.8,68.52 343.2,68.52 343.2,64.52 "/>
<g>
<path class="st5" d="M164.7,59.62"/>
<g>
<polyline class="st11" points="358.3,76.62 358.3,104.82 245.7,105.02 245.7,116.12 "/>
<path class="st12" d="M361.4,77.62c0,1.8-1.4,3.2-3.2,3.3c-1.8,0.1-3.2-1.4-3.3-3.2c0-1.8,1.4-3.2,3.2-3.3l0,0
C359.9,74.42,361.4,75.92,361.4,77.62z"/>
</g>
</g>
<path class="st12" d="M245.7,114.92c-1.8,0-3.2,1.3-3.3,3s1.4,3,3.2,3s3.2-1.3,3.3-3l0,0C248.9,116.32,247.4,114.92,245.7,114.92z
"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1034 158.07" style="enable-background:new 0 0 1034 158.07;" xml:space="preserve">
<style type="text/css">
.st0{fill:#67748A;}
.st1{fill:#FFFFFF;}
.st2{font-family:'Courier-Bold';}
.st3{font-size:16.7604px;}
.st4{fill:none;stroke:#45CFFF;stroke-width:2.793;stroke-linecap:round;stroke-linejoin:round;}
.st5{fill:#45CFFF;}
.st6{font-family:'Arial-BoldMT';}
</style>
<g>
<path class="st0" d="M516.7,39.8v56.33c0,4.17-3.8,7.54-8.5,7.54H14.6H8.5c-4.7,0-8.5-3.37-8.5-7.54V39.8c0-4.17,3.8-7.54,8.5-7.54
h499.7C512.9,32.26,516.7,35.63,516.7,39.8z"/>
<text transform="matrix(1 0 0 1 51.0379 69.3318)" class="st1 st2 st3">GET /test \n\nGET /test \n\nGET test \n\n</text>
<g>
<polyline class="st4" points="189.5,76.47 189.5,80.47 51.8,80.47 51.8,76.47 "/>
<polyline class="st4" points="331.3,76.47 331.3,80.47 195.6,80.47 195.6,76.47 "/>
<polyline class="st4" points="462.7,76.47 462.7,80.47 336.9,80.47 336.9,76.47 "/>
</g>
<g>
<text transform="matrix(1 0 0 1 78.8909 148.8896)" class="st5 st6 st3">1st request</text>
<g>
<line class="st4" x1="120.2" y1="93.57" x2="120.3" y2="129.57"/>
<path class="st5" d="M117.1,92.57c0-1.8,1.4-3.2,3.2-3.3c1.8,0,3.2,1.4,3.3,3.2c0,1.8-1.4,3.2-3.2,3.3l0,0
C118.5,95.77,117.1,94.37,117.1,92.57z"/>
</g>
</g>
<g>
<text transform="matrix(1 0 0 1 217.3594 148.8891)" class="st5 st6 st3">2nd request </text>
<g>
<line class="st4" x1="263.8" y1="93.57" x2="263.9" y2="129.57"/>
<path class="st5" d="M260.7,92.57c0-1.8,1.4-3.2,3.2-3.3c1.8,0,3.2,1.4,3.3,3.2c0,1.8-1.4,3.2-3.2,3.3l0,0
C262.1,95.77,260.7,94.37,260.7,92.57z"/>
</g>
</g>
<g>
<text transform="matrix(1 0 0 1 354.7297 148.8896)" class="st5 st6 st3">3rd request </text>
<g>
<line class="st4" x1="399.4" y1="93.57" x2="399.4" y2="129.57"/>
<path class="st5" d="M396.2,92.57c0-1.8,1.4-3.2,3.2-3.3c1.8,0,3.2,1.4,3.3,3.2c0,1.8-1.4,3.2-3.2,3.3l0,0
C397.7,95.77,396.2,94.37,396.2,92.57z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,113 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1035.4 429.9" style="enable-background:new 0 0 1035.4 429.9;" xml:space="preserve">
<style type="text/css">
.st0{fill:#576174;}
.st1{fill:#67748A;}
.st2{fill:none;stroke:#87005B;stroke-width:2.7934;stroke-linecap:round;stroke-linejoin:round;}
.st3{fill:#87005B;}
.st4{fill:#FFFFFF;}
.st5{font-family:'Courier-Bold';}
.st6{font-size:16.7604px;}
.st7{fill:none;stroke:#EA5B0C;stroke-width:2.793;stroke-linecap:round;stroke-linejoin:round;}
.st8{fill:#EA5B0C;}
.st9{fill:none;stroke:#CA70FF;stroke-width:2.7934;stroke-linecap:round;stroke-linejoin:round;}
.st10{fill:#CA70FF;}
.st11{fill:none;stroke:#FFDE00;stroke-width:2.793;stroke-linecap:round;stroke-linejoin:round;}
.st12{fill:#FFDE00;}
.st13{fill:none;stroke:#00CF4F;stroke-width:2.7934;stroke-linecap:round;stroke-linejoin:round;}
.st14{fill:#00CF4F;}
.st15{fill:none;stroke:#45CFFF;stroke-width:2.7934;stroke-linecap:round;stroke-linejoin:round;}
.st16{fill:none;stroke:#EA5B0C;stroke-width:2.7934;stroke-linecap:round;stroke-linejoin:round;}
.st17{font-family:'Arial-BoldMT';}
.st18{font-size:24px;}
.st19{fill:#45CFFF;}
</style>
<g>
<path class="st0" d="M755.8,391.8H8.5c-4.7,0-8.5-3.8-8.5-8.5v-153c0-4.7,3.8-8.5,8.5-8.5h747.3c4.7,0,8.5,3.8,8.5,8.5v153
C764.3,388,760.5,391.8,755.8,391.8z"/>
<path class="st1" d="M996.9,261.5h-427c-4.7,0-8.5-3.8-8.5-8.5V81.2c0-4.7,3.8-8.5,8.5-8.5h427c4.7,0,8.5,3.8,8.5,8.5V253
C1005.4,257.7,1001.5,261.5,996.9,261.5z"/>
<g>
<polyline class="st2" points="598.1,238.2 598.1,242.3 787.7,242.3 787.7,238.2 "/>
<g>
<line class="st2" x1="689.3" y1="342.2" x2="276.4" y2="343.3"/>
<g>
<line class="st2" x1="689.4" y1="253.3" x2="689.4" y2="342.1"/>
<path class="st3" d="M689.4,249.3c1.8,0,3.2,1.4,3.3,3.2c0,1.8-1.4,3.2-3.2,3.3c-1.8,0-3.2-1.4-3.3-3.2l0,0
C686.1,250.7,687.6,249.3,689.4,249.3z"/>
</g>
</g>
<path class="st3" d="M274.2,340.4c1.8,0,3.2,1.4,3.3,3.2s-1.4,3.2-3.2,3.3c-1.8,0-3.2-1.4-3.3-3.2l0,0
C270.9,341.8,272.4,340.4,274.2,340.4z"/>
</g>
<text transform="matrix(1 0 0 1 26.0923 262.5192)" class="st4 st5 st6">struct mg_http_message {</text>
<text transform="matrix(1 0 0 1 45.542 286.2874)" class="st4 st5 st6">struct mg_str method; uri, query, proto; </text>
<text transform="matrix(1 0 0 1 45.542 306.3874)" class="st4 st5 st6">struct mg_http_header headers[MG_MAX_HTTP_HEADERS];</text>
<text transform="matrix(1 0 0 1 45.542 326.4874)" class="st4 st5 st6">struct mg_str body;</text>
<text transform="matrix(1 0 0 1 45.542 346.5874)" class="st4 st5 st6">struct mg_str message; </text>
<g>
<g>
<polyline class="st7" points="621.1,89.6 621.1,78.6 215.4,79.7 215.3,269.4 "/>
<path class="st8" d="M624.3,90.6c0,1.8-1.4,3.2-3.2,3.3c-1.8,0-3.2-1.4-3.3-3.2c0-1.8,1.4-3.2,3.2-3.3l0,0
C622.8,87.4,624.3,88.8,624.3,90.6z"/>
</g>
<path class="st8" d="M212.3,269.4c0-1.8,1.4-3.2,3.2-3.3c1.8,0,3.2,1.4,3.3,3.2c0,1.8-1.4,3.2-3.2,3.3l0,0
C213.7,272.6,212.3,271.2,212.3,269.4z"/>
</g>
<g>
<polyline class="st9" points="597.2,183.6 593.1,183.6 593.1,127.8 597.2,127.8 "/>
<g>
<polyline class="st9" points="582.4,156.2 575.2,156.2 575,302.8 563.4,302.8 "/>
<path class="st10" d="M586.1,156.2c0-1.8-1.4-3.2-3.2-3.3c-1.8,0-3.2,1.4-3.3,3.2c0,1.8,1.4,3.2,3.2,3.3l0,0
C584.7,159.4,586.1,158,586.1,156.2z"/>
<path class="st10" d="M564.2,303c0-1.8-1.4-3.2-3.2-3.3c-1.8,0-3.2,1.4-3.3,3.2s1.4,3.2,3.2,3.3l0,0
C562.7,306.2,564.2,304.7,564.2,303z"/>
</g>
</g>
<text transform="matrix(1 0 0 1 601.1215 118.1291)" class="st4 st5 st6">POST /foo/bar/baz?aa=b&amp;cc=ddd HTTP/1.1</text>
<text transform="matrix(1 0 0 1 601.1215 138.2291)" class="st4 st5 st6">Content-Type: application/json</text>
<text transform="matrix(1 0 0 1 601.1215 158.3291)" class="st4 st5 st6">Content-Length: 19</text>
<text transform="matrix(1 0 0 1 601.1215 178.4291)" class="st4 st5 st6">Host: reqbin.com</text>
<g>
<g>
<g>
<polyline class="st11" points="712.3,90.6 712.4,67.6 277.8,67.6 279.7,269.3 "/>
<path class="st12" d="M715.6,90.8c0,1.8-1.4,3.2-3.2,3.3c-1.8,0-3.2-1.4-3.3-3.2c0-1.8,1.4-3.2,3.2-3.3l0,0
C714.1,87.6,715.6,89.1,715.6,90.8z"/>
</g>
<path class="st12" d="M276.5,269.4c0-1.8,1.4-3.2,3.2-3.3s3.2,1.4,3.3,3.2s-1.4,3.2-3.2,3.3l0,0
C278,272.6,276.5,271.2,276.5,269.4z"/>
</g>
<polyline class="st11" points="771.5,104.3 771.5,100.3 652.7,100.3 652.7,104.3 "/>
</g>
<g>
<g>
<g>
<polyline class="st13" points="836.3,90.6 836.3,56.6 342.8,56.6 344.7,268.3 "/>
<path class="st14" d="M839.5,90.6c0,1.8-1.4,3.2-3.2,3.3c-1.8,0-3.2-1.4-3.3-3.2c0-1.8,1.4-3.2,3.2-3.3l0,0
C838,87.4,839.5,88.9,839.5,90.6z"/>
</g>
<path class="st14" d="M341.4,269.3c0-1.8,1.4-3.2,3.2-3.3c1.8,0,3.2,1.4,3.3,3.2c0,1.8-1.4,3.2-3.2,3.3l0,0
C342.9,272.5,341.4,271.1,341.4,269.3z"/>
</g>
<polyline class="st15" points="981.4,104.2 981.4,100.2 902.6,100.2 902.6,104.2 "/>
</g>
<text transform="matrix(1 0 0 1 596.891 232.3658)" class="st4 st5 st6">{&quot;success&quot;: &quot;true&quot;}</text>
<polyline class="st16" points="643.1,104.4 643.1,100.4 599.3,100.4 599.3,104.4 "/>
<text transform="matrix(1 0 0 1 9.5506 207.5157)" class="st17 st18">Source Code</text>
<text transform="matrix(1 0 0 1 797.0799 32.3923)" class="st17 st18">HTTP message</text>
<text transform="matrix(1 0 0 1 26.0924 359.209)" class="st4 st5 st6">};</text>
<polyline class="st13" points="894.2,104.2 894.2,100.2 774.4,100.2 774.4,104.2 "/>
<g>
<g>
<polyline class="st15" points="941.3,90.6 941.3,45.6 412.8,45.6 414.7,268.3 "/>
<path class="st19" d="M944.5,90.6c0,1.8-1.4,3.2-3.2,3.3c-1.8,0-3.2-1.4-3.3-3.2c0-1.8,1.4-3.2,3.2-3.3l0,0
C943,87.4,944.5,88.9,944.5,90.6z"/>
</g>
<path class="st19" d="M411.4,269.3c0-1.8,1.4-3.2,3.2-3.3c1.8,0,3.2,1.4,3.3,3.2c0,1.8-1.4,3.2-3.2,3.3l0,0
C412.9,272.5,411.4,271.1,411.4,269.3z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1034 663.54" style="enable-background:new 0 0 1034 663.54;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#45CFFF;stroke-width:2.7934;stroke-linecap:round;stroke-linejoin:round;}
.st1{fill:#45CFFF;}
.st2{fill:#576174;}
.st3{fill:#FFFFFF;}
.st4{font-family:'Courier-Bold';}
.st5{font-size:16.7604px;}
.st6{font-family:'Arial-BoldMT';}
.st7{font-size:24px;}
.st8{fill:#67748A;}
.st9{fill:#8B9CBA;}
.st10{fill:none;stroke:#00CF4F;stroke-width:2.7934;stroke-linecap:round;stroke-linejoin:round;}
.st11{fill:#00CF4F;}
.st12{fill:none;stroke:#EA5B0C;stroke-width:2.793;stroke-linecap:round;stroke-linejoin:round;}
.st13{fill:#EA5B0C;}
.st14{fill:#414857;}
</style>
<g>
<g>
<polyline class="st0" points="498.5,624.21 498.5,628.21 447.9,628.21 447.9,624.21 "/>
<g>
<path class="st0" d="M491.8,844.81"/>
<g>
<path class="st1" d="M473.8,636.11c-1.8,0-3.2,1.4-3.3,3.2c0,1.8,1.4,3.2,3.2,3.3c1.8,0,3.2-1.4,3.3-3.2l0,0
C477.1,637.61,475.6,636.11,473.8,636.11z"/>
</g>
</g>
<path class="st1" d="M296.7,852.71c-1.8,0-3.2,1.3-3.3,3c0,1.7,1.4,3,3.2,3c1.8,0,3.2-1.3,3.3-3l0,0
C299.9,854.01,298.5,852.71,296.7,852.71z"/>
</g>
<g>
<path class="st2" d="M584.6,199.01H8.5c-4.7,0-8.5-3.8-8.5-8.5V60.71c0-4.7,3.8-8.5,8.5-8.5h576.1c4.7,0,8.5,3.8,8.5,8.5v129.8
C593.1,195.21,589.3,199.01,584.6,199.01z"/>
<g>
<g>
<g>
<text transform="matrix(1 0 0 1 28.7 90.8107)" class="st3 st4 st5">struct mg_http_part part;</text>
<text transform="matrix(1 0 0 1 28.7 110.9108)" class="st3 st4 st5">size_t pos = 0;</text>
<text transform="matrix(1 0 0 1 28.7 151.1107)" class="st3 st4 st5">pos = mg_http_next_multipart(&amp;hm-&gt;body, pos, &amp;part);</text>
<text transform="matrix(1 0 0 1 28.7 171.3107)" class="st3 st4 st5">pos = mg_http_next_multipart(&amp;hm-&gt;body, pos, &amp;part);</text>
</g>
</g>
</g>
<text transform="matrix(1 0 0 1 1.3738 40.559)" class="st6 st7">Source Code</text>
</g>
<g>
<g>
<path class="st8" d="M685.5,646.41H8.5c-4.7,0-8.5-3.8-8.5-8.5v-362.9c0-4.7,3.8-8.5,8.5-8.5h677c4.7,0,8.5,3.8,8.5,8.5v362.9
C694,642.61,690.2,646.41,685.5,646.41z"/>
</g>
<path class="st9" d="M640.2,629.41H26.3c-4.7,0-8.5-3.8-8.5-8.5v-106.5c0-4.7,3.8-8.5,8.5-8.5h613.9c4.7,0,8.5,3.8,8.5,8.5v106.5
C648.7,625.61,644.8,629.41,640.2,629.41z"/>
<path class="st9" d="M644,490.91H30.1c-4.7,0-8.5-3.8-8.5-8.5v-106.5c0-4.7,3.8-8.5,8.5-8.5H644c4.7,0,8.5,3.8,8.5,8.5v106.5
C652.5,487.11,648.7,490.91,644,490.91z"/>
<g>
<text transform="matrix(1 0 0 1 0.0299 253.0343)" class="st6 st7">HTTP message</text>
</g>
<g>
<text transform="matrix(1 0 0 1 28.7 302.8107)" class="st3 st4 st5">POST /upload HTTP/1.1 </text>
<text transform="matrix(1 0 0 1 28.7 322.9107)" class="st3 st4 st5">Content-Type: multipart/form-data; boundary=&quot;--xyz&quot;</text>
<text transform="matrix(1 0 0 1 28.7 363.1107)" class="st3 st4 st5">--xyz</text>
<text transform="matrix(1 0 0 1 28.7 383.3107)" class="st3 st4 st5">Content-Disposition: form-data; name=&quot;foo&quot;; filename=&quot;a.txt&quot;</text>
<text transform="matrix(1 0 0 1 28.7 403.4107)" class="st3 st4 st5">Content-Type: text/plain</text>
<text transform="matrix(1 0 0 1 28.7 443.6107)" class="st3 st4 st5">hello world</text>
<text transform="matrix(1 0 0 1 28.7 483.8107)" class="st3 st4 st5">--xyz---</text>
<text transform="matrix(1 0 0 1 28.7 524.0107)" class="st3 st4 st5">Content-Disposition: form-data; name=&quot;bar&quot;; filename=&quot;b.txt&quot;</text>
<text transform="matrix(1 0 0 1 28.7 544.2108)" class="st3 st4 st5">Content-Type: text/plain</text>
<text transform="matrix(1 0 0 1 28.7 584.4108)" class="st3 st4 st5">hello world again</text>
<text transform="matrix(1 0 0 1 28.7 624.6107)" class="st3 st4 st5">--xyz--</text>
</g>
</g>
<g>
<g>
<polyline class="st10" points="569.5,162.51 574.5,162.51 574.5,176.11 569.5,176.11 "/>
<g>
<path class="st0" d="M726.8,392.91"/>
<g>
<polyline class="st10" points="587.3,169.71 726.7,169.71 726.6,569.31 684.3,569.31 "/>
<path class="st11" d="M585,166.41c-1.8,0-3.2,1.4-3.3,3.2c0,1.8,1.4,3.2,3.2,3.3c1.8,0,3.2-1.4,3.3-3.2l0,0
C588.2,167.91,586.8,166.41,585,166.41z"/>
</g>
</g>
<path class="st11" d="M685,566.31c-1.7,0-3,1.3-3,3s1.3,3,3,3s3-1.3,3-3l0,0C688,567.61,686.6,566.31,685,566.31z"/>
</g>
<polyline class="st10" points="657.9,501.61 673.9,501.61 673.9,634.21 657.9,634.21 "/>
</g>
<g>
<g>
<polyline class="st12" points="569.5,143.21 574.5,143.21 574.5,156.81 569.5,156.81 "/>
<g>
<path class="st0" d="M726.8,254.61"/>
<g>
<polyline class="st12" points="587.3,150.41 710.7,150.41 710.6,431.01 684.3,431.01 "/>
<path class="st13" d="M585,147.11c-1.8,0-3.2,1.4-3.3,3.2s1.4,3.2,3.2,3.3c1.8,0,3.2-1.4,3.3-3.2l0,0
C588.2,148.61,586.8,147.11,585,147.11z"/>
</g>
</g>
<path class="st13" d="M685,428.01c-1.7,0-3,1.3-3,3s1.3,3,3,3s3-1.3,3-3l0,0C688,429.31,686.6,428.01,685,428.01z"/>
</g>
<polyline class="st12" points="657.9,363.31 673.9,363.31 673.9,495.91 657.9,495.91 "/>
</g>
<text transform="matrix(1 0 0 1 537.9671 462.1467)" class="st14 st6 st7">Part 1</text>
<text transform="matrix(1 0 0 1 537.9671 600.4373)" class="st14 st6 st7">Part 2</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1034 480.6" style="enable-background:new 0 0 1034 480.6;" xml:space="preserve">
<style type="text/css">
.st0{fill:#67748A;}
.st1{fill:#8B9CBA;}
.st2{fill:#FFFFFF;}
.st3{font-family:'Courier-Bold';}
.st4{font-size:16.7604px;}
.st5{fill:#414857;}
.st6{font-family:'Arial-BoldMT';}
.st7{font-size:30px;}
.st8{font-size:24px;}
.st9{fill:#576174;}
.st10{fill:none;stroke:#45CFFF;stroke-width:2.7934;stroke-linecap:round;stroke-linejoin:round;}
.st11{fill:#45CFFF;}
.st12{fill:none;stroke:#EA5B0C;stroke-width:2.793;stroke-linecap:round;stroke-linejoin:round;}
.st13{fill:#EA5B0C;}
.st14{fill:none;stroke:#EA5B0C;stroke-width:2.7934;stroke-linecap:round;stroke-linejoin:round;}
.st15{fill:none;stroke:#FFDE00;stroke-width:2.793;stroke-linecap:round;stroke-linejoin:round;}
.st16{fill:#FFDE00;}
</style>
<g>
<g>
<path class="st0" d="M908.4,454.9H272c-4.7,0-8.5-3.8-8.5-8.5V216c0-4.7,3.8-8.5,8.5-8.5h636.4c4.7,0,8.5,3.8,8.5,8.5v230.4
C916.9,451.1,913.1,454.9,908.4,454.9z"/>
<g>
<path class="st1" d="M898.6,430.8H284.7c-4.7,0-8.5-3.8-8.5-8.5V315.9c0-4.7,3.8-8.5,8.5-8.5h613.9c4.7,0,8.5,3.8,8.5,8.5v106.5
C907.1,427,903.3,430.8,898.6,430.8z"/>
<text transform="matrix(1 0 0 1 295.3688 243.086)" class="st2 st3 st4">POST /upload HTTP/1.1 </text>
<text transform="matrix(1 0 0 1 295.3688 263.186)" class="st2 st3 st4">Content-Type: multipart/form-data: boundary=&quot;--xyz&quot;</text>
<text transform="matrix(1 0 0 1 295.3688 303.386)" class="st2 st3 st4">--xyz</text>
<text transform="matrix(1 0 0 1 295.3688 323.586)" class="st2 st3 st4">Content-Disposition: form-data; name=&quot;foo&quot;; filename=&quot;a.txt&quot;</text>
<text transform="matrix(1 0 0 1 295.3688 343.686)" class="st2 st3 st4">Content-Type: text/plain</text>
<text transform="matrix(1 0 0 1 295.3688 383.886)" class="st2 st3 st4">hello world</text>
<text transform="matrix(1 0 0 1 295.3688 424.086)" class="st2 st3 st4">--xyz---</text>
</g>
</g>
<text transform="matrix(1 0 0 1 739.2766 414.2906)" class="st5 st6 st7">HTTP part</text>
<text transform="matrix(1 0 0 1 739.2764 193.4305)" class="st6 st8">HTTP message</text>
<path class="st9" d="M304.9,198H8.5c-4.7,0-8.5-3.8-8.5-8.5V64.8c0-4.7,3.8-8.5,8.5-8.5H305c4.7,0,8.5,3.8,8.5,8.5v124.7
C313.4,194.2,309.6,198,304.9,198z"/>
<g>
<g>
<text transform="matrix(1 0 0 1 25.2409 91.1617)" class="st2 st3 st4">struct mg_http_part {</text>
<text transform="matrix(1 0 0 1 47.0734 113.7079)" class="st2 st3 st4">struct mg_str name;</text>
<text transform="matrix(1 0 0 1 47.0734 133.8079)" class="st2 st3 st4">strucy mg_str filename;</text>
<text transform="matrix(1 0 0 1 47.0734 153.9079)" class="st2 st3 st4">struct mg_str body;</text>
</g>
</g>
<g>
<polyline class="st10" points="237.4,159 237.4,163 185.8,163 185.8,159 "/>
<g>
<polyline class="st10" points="211.7,348.3 349.5,348.4 349.5,355.6 "/>
<g>
<line class="st10" x1="211.6" y1="174.4" x2="211.6" y2="348.1"/>
<path class="st11" d="M211.6,170.3c-1.8,0-3.2,1.4-3.3,3.2c0,1.8,1.4,3.2,3.2,3.3c1.8,0,3.2-1.4,3.3-3.2l0,0
C214.8,171.8,213.4,170.3,211.6,170.3z"/>
</g>
</g>
<path class="st11" d="M349.6,353.6c-1.8,0-3.2,1.4-3.3,3.2s1.4,3.2,3.2,3.3c1.8,0,3.2-1.4,3.3-3.2l0,0
C352.8,355.1,351.4,353.6,349.6,353.6z"/>
<polyline class="st10" points="237.4,159 237.4,163 185.8,163 185.8,159 "/>
<polyline class="st10" points="407.3,371.1 407.3,367.1 293.3,367.1 293.3,371.1 "/>
</g>
<g>
<g>
<g>
<polyline class="st12" points="693.7,291.6 693.7,277.7 280.7,278.8 280.5,109.4 248.3,109.4 "/>
<path class="st13" d="M696.9,293.6c0,1.8-1.4,3.2-3.2,3.3c-1.8,0-3.2-1.4-3.3-3.2c0-1.8,1.4-3.2,3.2-3.3l0,0
C695.4,290.4,696.9,291.9,696.9,293.6z"/>
</g>
<path class="st13" d="M243.9,109.5c0-1.8,1.4-3.2,3.2-3.3c1.8,0,3.2,1.4,3.3,3.2c0,1.8-1.4,3.2-3.2,3.3l0,0
C245.3,112.7,243.9,111.3,243.9,109.5z"/>
</g>
<polyline class="st14" points="707.6,309.2 707.6,305.2 679.9,305.2 679.9,309.2 "/>
</g>
<text transform="matrix(1 0 0 1 0.8627 37.8099)" class="st6 st8">Source Code</text>
<text transform="matrix(1 0 0 1 25.2408 177.7241)" class="st2 st3 st4">};</text>
<g>
<g>
<g>
<polyline class="st15" points="863.8,291.5 863.7,218.6 329.7,218.7 329.6,130.3 298.4,130.3 "/>
<path class="st16" d="M867.1,293.6c0,1.8-1.4,3.2-3.2,3.3c-1.8,0-3.2-1.4-3.3-3.2c0-1.8,1.4-3.2,3.2-3.3l0,0
C865.6,290.3,867.1,291.8,867.1,293.6z"/>
</g>
<path class="st16" d="M293,130.1c0-1.8,1.4-3.2,3.2-3.3c1.8,0,3.2,1.4,3.3,3.2c0,1.8-1.4,3.2-3.2,3.3l0,0
C294.4,133.3,293,131.9,293,130.1z"/>
</g>
<polyline class="st15" points="888.7,309.1 888.7,305.1 838.9,305.1 838.9,309.1 "/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1034 331.2" style="enable-background:new 0 0 1034 331.2;" xml:space="preserve">
<style type="text/css">
.st0{fill:#576174;}
.st1{fill:#67748A;}
.st2{fill:#FFFFFF;}
.st3{font-family:'Courier-Bold';}
.st4{font-size:16.7604px;}
.st5{font-family:'Arial-BoldMT';}
.st6{font-size:24px;}
.st7{fill:none;stroke:#45CFFF;stroke-width:2.7934;stroke-linecap:round;stroke-linejoin:round;}
.st8{fill:#45CFFF;}
.st9{fill:none;stroke:#EA5B0C;stroke-width:2.793;stroke-linecap:round;stroke-linejoin:round;}
.st10{fill:#EA5B0C;}
.st11{fill:none;stroke:#EA5B0C;stroke-width:2.7934;stroke-linecap:round;stroke-linejoin:round;}
.st12{fill:none;stroke:#00CF4F;stroke-width:2.7934;stroke-linecap:round;stroke-linejoin:round;}
.st13{fill:#00CF4F;}
</style>
<g>
<path class="st0" d="M541.43,184.2H8.53c-4.7,0-8.5-3.8-8.5-8.5V54.8c0-4.7,3.8-8.5,8.5-8.5h532.9c4.7,0,8.5,3.8,8.5,8.5v120.9
C549.93,180.4,546.13,184.2,541.43,184.2z"/>
<g>
<path class="st1" d="M829.23,315.5h-370c-4.7,0-8.5-3.8-8.5-8.5V157c0-4.7,3.8-8.5,8.5-8.5h370c4.7,0,8.5,3.8,8.5,8.5v150
C837.83,311.7,833.93,315.5,829.23,315.5z"/>
<g>
<text transform="matrix(1 0 0 1 490.6096 184.0905)" class="st2 st3 st4">HTTP/1.1 200 OK</text>
<text transform="matrix(1 0 0 1 490.6096 204.1905)" class="st2 st3 st4">Content-Length; 13</text>
<text transform="matrix(1 0 0 1 490.6096 224.2905)" class="st2 st3 st4">X-Header-1: additional value 1</text>
<text transform="matrix(1 0 0 1 490.6096 244.3905)" class="st2 st3 st4">X-Header-2: additional value 2</text>
<text transform="matrix(1 0 0 1 490.8657 293.1559)" class="st2 st3 st4">Hello, world!</text>
</g>
</g>
<text transform="matrix(1 0 0 1 660.5036 137.3383)" class="st5 st6">HTTP message</text>
<g>
<g>
<text transform="matrix(1 0 0 1 20.7331 85.1617)" class="st2 st3 st4">mg_http_reply(c, 200</text>
<text transform="matrix(1 0 0 1 159.5657 113.7079)" class="st2 st3 st4">&quot;X-Header-1: additional value 1\r\n&quot;</text>
<text transform="matrix(1 0 0 1 159.5657 133.8079)" class="st2 st3 st4">&quot;X-Header-2: additional value 2\r\n&quot;,</text>
<text transform="matrix(1 0 0 1 159.5657 153.9079)" class="st2 st3 st4">&quot;Hello, %s!&quot;, &quot;world&quot;);</text>
</g>
</g>
<g>
<polyline class="st7" points="362.93,159 362.93,163 167.33,163 167.33,159 "/>
<g>
<polyline class="st7" points="265.93,256.6 552.13,256.7 552.13,264.4 "/>
<g>
<line class="st7" x1="265.83" y1="174.4" x2="265.83" y2="256.6"/>
<path class="st8" d="M265.83,170.3c-1.8,0-3.2,1.4-3.3,3.2c0,1.8,1.4,3.2,3.2,3.3c1.8,0,3.2-1.4,3.3-3.2l0,0
C269.03,171.8,267.53,170.3,265.83,170.3z"/>
</g>
</g>
<path class="st8" d="M552.23,261.6c-1.8,0-3.2,1.4-3.3,3.2c0,1.8,1.4,3.2,3.2,3.3c1.8,0,3.2-1.4,3.3-3.2l0,0
C555.43,263.1,554.03,261.6,552.23,261.6z"/>
</g>
<g>
<g>
<g>
<polyline class="st9" points="206.33,53.6 206.43,37.6 596.43,38.7 596.53,161.3 "/>
<path class="st10" d="M203.23,54.6c0,1.8,1.4,3.2,3.2,3.3c1.8,0,3.2-1.4,3.3-3.2c0-1.8-1.4-3.2-3.2-3.3l0,0
C204.63,51.4,203.23,52.8,203.23,54.6z"/>
</g>
<path class="st10" d="M599.83,164c0-1.8-1.4-3.2-3.2-3.3c-1.8,0-3.2,1.4-3.3,3.2c0,1.8,1.4,3.2,3.2,3.3l0,0
C598.33,167.2,599.83,165.8,599.83,164z"/>
</g>
<polyline class="st11" points="192.43,69.2 192.43,65.2 220.23,65.2 220.23,69.2 "/>
</g>
<text transform="matrix(1 0 0 1 -1.917133e-04 35.0037)" class="st5 st6">Source Code</text>
<g>
<polyline class="st12" points="160.13,135.9 156.03,135.9 156.03,100.1 160.13,100.1 "/>
<g>
<line class="st12" x1="123.93" y1="231" x2="469.73" y2="231"/>
<polyline class="st12" points="144.73,117.2 123.83,117.2 123.73,230.8 "/>
<path class="st13" d="M147.83,117.2c0-1.8-1.4-3.2-3.2-3.3c-1.8,0-3.2,1.4-3.3,3.2c0,1.8,1.4,3.2,3.2,3.3l0,0
C146.33,120.4,147.83,119,147.83,117.2z"/>
<path class="st13" d="M474.83,230.8c0-1.8-1.4-3.2-3.2-3.3c-1.8,0-3.2,1.4-3.3,3.2c0,1.8,1.4,3.2,3.2,3.3l0,0
C473.43,234,474.83,232.5,474.83,230.8z"/>
</g>
</g>
<polyline class="st7" points="489.03,279.6 489.03,275.6 620.63,275.6 620.63,279.6 "/>
<polyline class="st12" points="487.63,248.3 483.63,248.3 483.63,211.5 487.63,211.5 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 869.6 185.67" style="enable-background:new 0 0 869.6 185.67;" xml:space="preserve">
<style type="text/css">
.st0{font-family:'Arial-BoldMT';}
.st1{font-size:24px;}
.st2{opacity:0.2;fill:#00983A;enable-background:new ;}
.st3{fill:none;stroke:#7A8387;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round;}
.st4{fill:#00983A;}
.st5{fill:none;stroke:#576174;stroke-miterlimit:10;}
.st6{fill:#F2F3F3;stroke:#7A8387;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round;}
.st7{fill:none;stroke:#576174;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round;}
.st8{font-family:'Courier-Bold';}
.st9{font-size:16.7604px;}
.st10{fill:none;stroke:#00983A;stroke-linecap:round;stroke-linejoin:round;}
.st11{fill:#576174;}
.st12{font-family:'Courier';}
.st13{fill:none;}
.st14{fill:#7A8387;}
</style>
<g>
<text transform="matrix(1 0 0 1 1.0956 32.4549)" class="st0 st1">Memory</text>
<polyline class="st2" points="424.58,115.4 219.88,115.4 219.88,90.7 424.58,90.7 "/>
<g>
<g>
<g>
<polyline class="st3" points="458.98,115.4 218.98,115.4 218.98,90.7 458.98,90.7 "/>
<polyline class="st4" points="219.18,115.1 2.98,115.1 2.98,91.3 219.18,91.3 "/>
<line class="st3" x1="2.98" y1="69.2" x2="2.98" y2="130.4"/>
<line class="st5" x1="219.78" y1="69.2" x2="219.78" y2="130.4"/>
<line class="st5" x1="425.28" y1="69.2" x2="425.28" y2="130.4"/>
<path class="st6" d="M458.98,115.4"/>
<path class="st7" d="M33.98,114.6"/>
<path class="st7" d="M33.98,90.9"/>
<path class="st7" d="M2.98,90.7h216.8v24.9l-216.8-0.2C2.98,115.4,3.18,90.7,2.98,90.7z"/>
<line class="st3" x1="425.18" y1="90.8" x2="425.18" y2="115.3"/>
<path class="st6" d="M251.98,115.6"/>
<path class="st6" d="M251.98,91.9"/>
</g>
<g>
<text transform="matrix(1 0 0 1 -2.290726e-04 175.3858)" class="st4 st8 st9">buf</text>
</g>
<g>
<g>
<line class="st10" x1="2.68" y1="158.2" x2="2.68" y2="137.2"/>
<g>
<polygon class="st4" points="0.28,137.6 5.08,137.6 2.68,135.1 "/>
</g>
</g>
</g>
<g>
<g>
<text transform="matrix(1 0 0 1 214.6515 175.3858)" class="st4 st8 st9">len</text>
</g>
</g>
<g>
<g>
<line class="st10" x1="219.78" y1="158.2" x2="219.78" y2="137.2"/>
<g>
<polygon class="st4" points="217.38,137.6 222.18,137.6 219.78,135.1 "/>
</g>
</g>
</g>
<g>
<g>
<text transform="matrix(1 0 0 1 421.6186 175.3858)" class="st4 st8 st9">size</text>
</g>
</g>
<g>
<g>
<line class="st10" x1="425.38" y1="158.2" x2="425.38" y2="137.2"/>
<g>
<polygon class="st4" points="422.98,137.6 427.78,137.6 425.38,135.1 "/>
</g>
</g>
</g>
<g>
<g>
<text transform="matrix(1 0 0 1 93.1143 69.4458)" class="st11 st12 st9">data</text>
</g>
<g>
<g>
<text transform="matrix(1 0 0 1 273.8231 69.4458)" class="st11 st12 st9">free space</text>
</g>
</g>
<g>
<g>
<line class="st5" x1="132.98" y1="76.3" x2="13.98" y2="76.3"/>
<g>
<polygon class="st11" points="14.28,78.7 14.28,73.8 11.88,76.3 "/>
</g>
</g>
</g>
<g>
<line class="st13" x1="123.88" y1="76.3" x2="210.88" y2="76.3"/>
<g>
<line class="st5" x1="123.88" y1="76.3" x2="208.78" y2="76.3"/>
<g>
<polygon class="st11" points="208.48,73.8 208.48,78.7 210.88,76.3 "/>
</g>
</g>
</g>
<g>
<g>
<line class="st5" x1="320.38" y1="76.3" x2="414.28" y2="76.3"/>
<g>
<polygon class="st14" points="413.98,73.8 413.98,78.7 416.38,76.3 "/>
</g>
</g>
</g>
<g>
<g>
<line class="st5" x1="322.28" y1="76.3" x2="233.28" y2="76.3"/>
<g>
<polygon class="st14" points="233.68,78.7 233.68,73.8 231.18,76.3 "/>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 869 124.66" style="enable-background:new 0 0 869 124.66;" xml:space="preserve">
<style type="text/css">
.st0{fill:#F2F3F3;stroke:#7A8387;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round;}
.st1{fill:none;stroke:#7A8387;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round;}
.st2{fill:#00983A;}
.st3{font-family:'Courier-Bold';}
.st4{font-size:16.7604px;}
.st5{fill:none;stroke:#00983A;stroke-linecap:round;stroke-linejoin:round;}
.st6{fill:#7A8387;}
</style>
<g>
<g>
<g>
<polyline class="st0" points="830.92,78.4 5.52,78.4 5.52,53.7 830.92,53.6 "/>
<line class="st1" x1="5.52" y1="32.2" x2="5.52" y2="93.4"/>
<path class="st0" d="M830.92,78.4"/>
<line class="st1" x1="36.52" y1="53.9" x2="36.52" y2="77.6"/>
<line class="st1" x1="68.92" y1="53.9" x2="68.92" y2="77.6"/>
<line class="st1" x1="101.42" y1="53.9" x2="101.42" y2="77.6"/>
<line class="st1" x1="133.72" y1="53.9" x2="133.72" y2="77.6"/>
<line class="st1" x1="166.22" y1="53.9" x2="166.22" y2="77.6"/>
<line class="st1" x1="198.52" y1="54.6" x2="198.52" y2="78.3"/>
<line class="st1" x1="230.92" y1="54.6" x2="230.92" y2="78.3"/>
</g>
<g>
<text transform="matrix(1 0 0 1 59.1908 40.8585)" class="st2 st3 st4">buf=NULL, len=0, size=0 </text>
</g>
<g>
<text transform="matrix(1 0 0 1 -1.811981e-04 109.9024)" class="st2 st3 st4">0</text>
</g>
<g>
<g>
<g>
<line class="st5" x1="46.72" y1="37.7" x2="17.72" y2="37.7"/>
<g>
<polygon class="st2" points="18.12,40.1 18.12,35.2 15.62,37.7 "/>
</g>
</g>
</g>
</g>
<g>
<path class="st6" d="M511.52,66.4c0,0.9-0.7,1.6-1.6,1.6s-1.6-0.7-1.6-1.6s0.7-1.6,1.6-1.6S511.52,65.5,511.52,66.4z"/>
<path class="st6" d="M525.32,66.4c0,0.9-0.7,1.6-1.6,1.6s-1.6-0.7-1.6-1.6s0.7-1.6,1.6-1.6S525.32,65.5,525.32,66.4z"/>
<path class="st6" d="M539.02,66.4c0,0.9-0.7,1.6-1.6,1.6c-0.9,0-1.6-0.7-1.6-1.6s0.7-1.6,1.6-1.6S539.02,65.5,539.02,66.4z"/>
<path class="st6" d="M552.82,66.4c0,0.9-0.7,1.6-1.6,1.6s-1.6-0.7-1.6-1.6s0.7-1.6,1.6-1.6S552.82,65.5,552.82,66.4z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,185 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 869.6 127.5" style="enable-background:new 0 0 869.6 127.5;" xml:space="preserve">
<style type="text/css">
.st0{opacity:0.2;fill:#00983A;enable-background:new ;}
.st1{fill:#F2F3F3;}
.st2{fill:none;stroke:#7A8387;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round;}
.st3{fill:#00983A;}
.st4{fill:#F2F3F3;stroke:#7A8387;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round;}
.st5{fill:none;stroke:#576174;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round;}
.st6{fill:none;stroke:#576174;stroke-width:1.2;stroke-linecap:square;stroke-linejoin:round;}
.st7{font-family:'Courier-Bold';}
.st8{font-size:16.7604px;}
.st9{fill:none;stroke:#00983A;stroke-linecap:round;stroke-linejoin:round;}
.st10{fill:#7A8387;}
.st11{fill:#576174;}
.st12{fill:none;stroke:#576174;stroke-linecap:round;stroke-linejoin:round;}
.st13{fill:none;stroke:#7A8387;stroke-linecap:round;stroke-linejoin:round;}
.st14{fill:#333A45;}
</style>
<g>
<g>
<polyline class="st0" points="577.2,72.8 282.4,72.8 282.4,48 577.2,48 "/>
<g>
<g>
<g>
<polyline class="st1" points="122.6,72.8 5.9,72.8 5.9,48 122.6,48 "/>
<polyline class="st2" points="831.51,72.8 5.5,72.8 5.5,48 831.51,48 "/>
<polyline class="st3" points="282.7,72.5 122.5,72.5 122.5,47.7 282.7,47.7 "/>
<line class="st2" x1="122.5" y1="26.6" x2="122.5" y2="87.8"/>
<line class="st2" x1="283.3" y1="26.6" x2="283.3" y2="87.8"/>
<line class="st2" x1="576.8" y1="26.6" x2="576.8" y2="87.8"/>
<path class="st4" d="M831.51,72.8"/>
<line class="st5" x1="153.5" y1="48.3" x2="153.5" y2="72"/>
<line class="st2" x1="36.5" y1="48.3" x2="36.5" y2="72"/>
<line class="st2" x1="67.6" y1="48.3" x2="67.6" y2="72"/>
<line class="st5" x1="186" y1="48.3" x2="186" y2="72"/>
<line class="st5" x1="218.4" y1="48.3" x2="218.4" y2="72"/>
<line class="st5" x1="250.9" y1="48.3" x2="250.9" y2="72"/>
<line class="st2" x1="315.9" y1="48.3" x2="315.9" y2="72"/>
<line class="st2" x1="348.4" y1="48.3" x2="348.4" y2="72"/>
<line class="st2" x1="381.2" y1="48.3" x2="381.2" y2="72"/>
<line class="st2" x1="413.8" y1="48.3" x2="413.8" y2="72"/>
<line class="st2" x1="446.3" y1="48.3" x2="446.3" y2="72"/>
<line class="st2" x1="478.9" y1="48.3" x2="478.9" y2="72"/>
<line class="st5" x1="511.51" y1="48.3" x2="511.51" y2="72"/>
<line class="st2" x1="511.51" y1="48.3" x2="511.51" y2="72"/>
<line class="st5" x1="544.01" y1="48.3" x2="544.01" y2="72"/>
<line class="st2" x1="544.1" y1="48.3" x2="544.1" y2="72"/>
<line class="st6" x1="283.3" y1="73" x2="122.5" y2="72.8"/>
<line class="st6" x1="122.5" y1="48" x2="283.3" y2="48.1"/>
<path class="st4" d="M385.5,73"/>
<path class="st4" d="M385.5,49.3"/>
</g>
<g>
<text transform="matrix(1 0 0 1 127.5176 105.8013)" class="st3 st7 st8">buf</text>
</g>
<g>
<g>
<line class="st9" x1="122.2" y1="105.6" x2="122.2" y2="94.6"/>
<g>
<polygon class="st3" points="119.8,94.9 124.6,94.9 122.2,92.5 "/>
</g>
</g>
</g>
<g>
<text transform="matrix(1 0 0 1 286.6495 105.8008)" class="st3 st7 st8">len=5</text>
</g>
<g>
<g>
<line class="st9" x1="283.3" y1="105.6" x2="283.3" y2="94.6"/>
<g>
<polygon class="st3" points="280.9,94.9 285.8,94.9 283.3,92.5 "/>
</g>
</g>
</g>
<g>
<text transform="matrix(1 0 0 1 581.4624 105.8013)" class="st3 st7 st8">size=16</text>
</g>
<g>
<g>
<line class="st9" x1="576.9" y1="105.6" x2="576.9" y2="94.6"/>
<g>
<polygon class="st3" points="574.51,94.9 579.4,94.9 576.9,92.5 "/>
</g>
</g>
</g>
<g>
<path class="st10" d="M687.51,60.7c0,0.9-0.7,1.6-1.6,1.6s-1.6-0.7-1.6-1.6c0-0.9,0.7-1.6,1.6-1.6S687.51,59.9,687.51,60.7z"/>
<path class="st10" d="M701.2,60.7c0,0.9-0.7,1.6-1.6,1.6s-1.6-0.7-1.6-1.6c0-0.9,0.7-1.6,1.6-1.6S701.2,59.9,701.2,60.7z"/>
<path class="st10" d="M714.9,60.7c0,0.9-0.7,1.6-1.6,1.6c-0.9,0-1.6-0.7-1.6-1.6c0-0.9,0.7-1.6,1.6-1.6
C714.2,59.1,714.9,59.9,714.9,60.7z"/>
<path class="st10" d="M728.7,60.7c0,0.9-0.7,1.6-1.6,1.6s-1.6-0.7-1.6-1.6c0-0.9,0.7-1.6,1.6-1.6
C728.01,59.1,728.7,59.9,728.7,60.7z"/>
</g>
<g>
<path class="st10" d="M82.8,60.7c0,0.9-0.7,1.6-1.6,1.6s-1.6-0.7-1.6-1.6c0-0.9,0.7-1.6,1.6-1.6S82.8,59.9,82.8,60.7z"/>
<path class="st10" d="M96.5,60.7c0,0.9-0.7,1.6-1.6,1.6s-1.6-0.7-1.6-1.6c0-0.9,0.7-1.6,1.6-1.6C95.8,59.1,96.5,59.9,96.5,60.7
z"/>
<path class="st10" d="M110.2,60.7c0,0.9-0.7,1.6-1.6,1.6s-1.6-0.7-1.6-1.6c0-0.9,0.7-1.6,1.6-1.6S110.2,59.9,110.2,60.7z"/>
</g>
<g>
<g>
<text transform="matrix(1 0 0 1 180.6541 26.8123)" class="st11 st7 st8">data</text>
</g>
<g>
<text transform="matrix(1 0 0 1 387.363 26.8123)" class="st10 st7 st8">free space</text>
</g>
<g>
<g>
<line class="st12" x1="272.2" y1="33.6" x2="133.5" y2="33.6"/>
<g>
<polygon class="st11" points="271.9,36.2 273.5,33.6 271.9,31.1 271.8,31.1 271.8,36.2 "/>
</g>
<g>
<polygon class="st11" points="133.8,36.1 133.8,31.2 131.4,33.6 "/>
</g>
</g>
</g>
<g>
<g>
<line class="st13" x1="563.7" y1="33.6" x2="295.9" y2="33.6"/>
<g>
<polygon class="st10" points="563.4,36.1 563.4,31.2 565.8,33.6 "/>
</g>
<g>
<polygon class="st10" points="296.2,36.1 296.2,31.2 293.7,33.6 "/>
</g>
</g>
</g>
<g>
<text transform="matrix(1 0 0 1 615.4679 25.1898)" class="st10 st7 st8">non-allocated space</text>
</g>
<g>
<g>
<line class="st13" x1="593.7" y1="33.6" x2="829.7" y2="33.6"/>
<g>
<polygon class="st10" points="829.4,31.2 829.4,36.1 831.8,33.6 "/>
</g>
</g>
</g>
<g>
<g>
<line class="st13" x1="678.7" y1="33.6" x2="589.7" y2="33.6"/>
<g>
<polygon class="st10" points="590.01,36.1 590.01,31.2 587.6,33.6 "/>
</g>
</g>
</g>
</g>
<g>
<g>
<text transform="matrix(1 0 0 1 133.1212 64.9593)" class="st14 st7 st8">h</text>
</g>
</g>
<g>
<g>
<text transform="matrix(1 0 0 1 165.1564 64.9593)" class="st14 st7 st8">e</text>
</g>
</g>
<g>
<g>
<text transform="matrix(1 0 0 1 196.8744 64.9593)" class="st14 st7 st8">l</text>
</g>
</g>
<g>
<g>
<text transform="matrix(1 0 0 1 229.8611 64.9593)" class="st14 st7 st8">l</text>
</g>
</g>
<g>
<g>
<text transform="matrix(1 0 0 1 262.2135 64.9593)" class="st14 st7 st8">o</text>
</g>
</g>
</g>
</g>
</g>
<g>
<text transform="matrix(1 0 0 1 1.811981e-04 105.8011)" class="st3 st7 st8">0</text>
</g>
<line class="st2" x1="5.5" y1="26.4" x2="5.5" y2="87.6"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -0,0 +1,347 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 869.6 247.73" style="enable-background:new 0 0 869.6 247.73;" xml:space="preserve">
<style type="text/css">
.st0{opacity:0.2;fill:#00983A;enable-background:new ;}
.st1{fill:#00983A;}
.st2{fill:#FF80A4;}
.st3{fill:#F2F3F3;}
.st4{fill:none;stroke:#7A8387;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round;}
.st5{fill:#F2F3F3;stroke:#7A8387;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round;}
.st6{fill:none;stroke:#576174;stroke-width:1.2;stroke-linecap:round;stroke-linejoin:round;}
.st7{fill:none;stroke:#576174;stroke-width:1.2;stroke-linejoin:round;}
.st8{fill:none;stroke:#576174;stroke-width:1.2;stroke-linecap:square;stroke-linejoin:round;}
.st9{font-family:'Courier-Bold';}
.st10{font-size:16.7604px;}
.st11{fill:none;stroke:#00983A;stroke-linecap:round;stroke-linejoin:round;}
.st12{fill:#7A8387;}
.st13{fill:#576174;}
.st14{fill:none;stroke:#576174;stroke-linecap:round;stroke-linejoin:round;}
.st15{fill:none;stroke:#7A8387;stroke-linecap:round;stroke-linejoin:round;}
.st16{fill:#333A45;}
</style>
<g>
<g>
<polyline class="st0" points="577.2,72.8 282.4,72.8 282.4,48 577.2,48 "/>
<g>
<g>
<g>
<rect x="250.9" y="47.7" class="st1" width="32.4" height="24.8"/>
<rect x="153.5" y="47.7" class="st2" width="97.3" height="24.8"/>
<polyline class="st3" points="122.6,72.8 5.9,72.8 5.9,48 122.6,48 "/>
<polyline class="st4" points="831.51,72.8 5.5,72.8 5.5,48 831.51,48 "/>
<rect x="122.5" y="47.7" class="st1" width="31" height="24.8"/>
<line class="st4" x1="122.5" y1="26.6" x2="122.5" y2="87.8"/>
<line class="st4" x1="576.8" y1="26.6" x2="576.8" y2="87.8"/>
<path class="st5" d="M831.51,72.8"/>
<line class="st6" x1="153.5" y1="48.3" x2="153.5" y2="72"/>
<line class="st4" x1="36.5" y1="48.3" x2="36.5" y2="72"/>
<line class="st4" x1="67.6" y1="48.3" x2="67.6" y2="72"/>
<line class="st6" x1="186" y1="48.3" x2="186" y2="72"/>
<line class="st6" x1="218.4" y1="48.3" x2="218.4" y2="72"/>
<line class="st7" x1="250.9" y1="73" x2="250.81" y2="47.7"/>
<line class="st4" x1="315.9" y1="48.3" x2="315.9" y2="72"/>
<line class="st4" x1="348.4" y1="48.3" x2="348.4" y2="72"/>
<line class="st4" x1="381.2" y1="48.3" x2="381.2" y2="72"/>
<line class="st4" x1="413.8" y1="48.3" x2="413.8" y2="72"/>
<line class="st4" x1="446.3" y1="48.3" x2="446.3" y2="72"/>
<line class="st4" x1="478.9" y1="48.3" x2="478.9" y2="72"/>
<line class="st6" x1="511.51" y1="48.3" x2="511.51" y2="72"/>
<line class="st4" x1="511.51" y1="48.3" x2="511.51" y2="72"/>
<line class="st6" x1="544.01" y1="48.3" x2="544.01" y2="72"/>
<line class="st4" x1="544.1" y1="48.3" x2="544.1" y2="72"/>
<line class="st8" x1="283.3" y1="73" x2="122.5" y2="72.8"/>
<line class="st8" x1="122.5" y1="48" x2="283.3" y2="48.1"/>
<path class="st5" d="M385.5,73"/>
<path class="st5" d="M385.5,49.3"/>
<line class="st4" x1="283.3" y1="26.6" x2="283.3" y2="87.8"/>
</g>
<g>
<text transform="matrix(1 0 0 1 127.5176 105.8013)" class="st1 st9 st10">buf</text>
</g>
<g>
<g>
<line class="st11" x1="122.2" y1="105.6" x2="122.2" y2="94.6"/>
<g>
<polygon class="st1" points="119.8,94.9 124.6,94.9 122.2,92.5 "/>
</g>
</g>
</g>
<g>
<text transform="matrix(1 0 0 1 286.6495 105.8008)" class="st1 st9 st10">len=5</text>
</g>
<g>
<g>
<line class="st11" x1="283.3" y1="105.6" x2="283.3" y2="94.6"/>
<g>
<polygon class="st1" points="280.9,94.9 285.8,94.9 283.3,92.5 "/>
</g>
</g>
</g>
<g>
<text transform="matrix(1 0 0 1 581.4624 105.8013)" class="st1 st9 st10">size=16</text>
</g>
<g>
<g>
<line class="st11" x1="576.9" y1="105.6" x2="576.9" y2="94.6"/>
<g>
<polygon class="st1" points="574.51,94.9 579.4,94.9 576.9,92.5 "/>
</g>
</g>
</g>
<g>
<path class="st12" d="M687.51,60.7c0,0.9-0.7,1.6-1.6,1.6s-1.6-0.7-1.6-1.6c0-0.9,0.7-1.6,1.6-1.6S687.51,59.9,687.51,60.7z"/>
<path class="st12" d="M701.2,60.7c0,0.9-0.7,1.6-1.6,1.6s-1.6-0.7-1.6-1.6c0-0.9,0.7-1.6,1.6-1.6S701.2,59.9,701.2,60.7z"/>
<path class="st12" d="M714.9,60.7c0,0.9-0.7,1.6-1.6,1.6c-0.9,0-1.6-0.7-1.6-1.6c0-0.9,0.7-1.6,1.6-1.6
C714.2,59.1,714.9,59.9,714.9,60.7z"/>
<path class="st12" d="M728.7,60.7c0,0.9-0.7,1.6-1.6,1.6s-1.6-0.7-1.6-1.6c0-0.9,0.7-1.6,1.6-1.6
C728.01,59.1,728.7,59.9,728.7,60.7z"/>
</g>
<g>
<path class="st12" d="M82.8,60.7c0,0.9-0.7,1.6-1.6,1.6s-1.6-0.7-1.6-1.6c0-0.9,0.7-1.6,1.6-1.6S82.8,59.9,82.8,60.7z"/>
<path class="st12" d="M96.5,60.7c0,0.9-0.7,1.6-1.6,1.6s-1.6-0.7-1.6-1.6c0-0.9,0.7-1.6,1.6-1.6C95.8,59.1,96.5,59.9,96.5,60.7
z"/>
<path class="st12" d="M110.2,60.7c0,0.9-0.7,1.6-1.6,1.6s-1.6-0.7-1.6-1.6c0-0.9,0.7-1.6,1.6-1.6S110.2,59.9,110.2,60.7z"/>
</g>
<g>
<g>
<text transform="matrix(1 0 0 1 180.6541 26.8123)" class="st13 st9 st10">data</text>
</g>
<g>
<text transform="matrix(1 0 0 1 387.363 26.8123)" class="st12 st9 st10">free space</text>
</g>
<g>
<g>
<line class="st14" x1="272.2" y1="33.6" x2="133.5" y2="33.6"/>
<g>
<polygon class="st13" points="271.9,36.2 273.5,33.6 271.9,31.1 271.8,31.1 271.8,36.2 "/>
</g>
<g>
<polygon class="st13" points="133.8,36.1 133.8,31.2 131.4,33.6 "/>
</g>
</g>
</g>
<g>
<g>
<line class="st15" x1="563.7" y1="33.6" x2="295.9" y2="33.6"/>
<g>
<polygon class="st12" points="563.4,36.1 563.4,31.2 565.8,33.6 "/>
</g>
<g>
<polygon class="st12" points="296.2,36.1 296.2,31.2 293.7,33.6 "/>
</g>
</g>
</g>
<g>
<text transform="matrix(1 0 0 1 615.4679 25.1898)" class="st12 st9 st10">non-allocated space</text>
</g>
<g>
<g>
<line class="st15" x1="593.7" y1="33.6" x2="829.7" y2="33.6"/>
<g>
<polygon class="st12" points="829.4,31.2 829.4,36.1 831.8,33.6 "/>
</g>
</g>
</g>
<g>
<g>
<line class="st15" x1="678.7" y1="33.6" x2="589.7" y2="33.6"/>
<g>
<polygon class="st12" points="590.01,36.1 590.01,31.2 587.6,33.6 "/>
</g>
</g>
</g>
</g>
<g>
<g>
<text transform="matrix(1 0 0 1 133.1212 64.9593)" class="st16 st9 st10">h</text>
</g>
</g>
<g>
<g>
<g>
<text transform="matrix(1 0 0 1 165.1564 64.9593)" class="st16 st9 st10">e</text>
</g>
</g>
</g>
<g>
<g>
<g>
<text transform="matrix(1 0 0 1 196.8744 64.9593)" class="st16 st9 st10">l</text>
</g>
</g>
</g>
<g>
<g>
<g>
<text transform="matrix(1 0 0 1 229.8611 64.9593)" class="st16 st9 st10">l</text>
</g>
</g>
</g>
<g>
<g>
<text transform="matrix(1 0 0 1 262.2135 64.9593)" class="st16 st9 st10">o</text>
</g>
</g>
</g>
</g>
</g>
<g>
<text transform="matrix(1 0 0 1 1.811981e-04 105.8011)" class="st1 st9 st10">0</text>
</g>
<line class="st4" x1="5.5" y1="26.4" x2="5.5" y2="87.6"/>
</g>
<g>
<g>
<polyline class="st0" points="577.2,207.99 186.3,207.99 186.3,183.19 577.2,183.19 "/>
<g>
<g>
<g>
<polyline class="st3" points="122.6,207.99 5.9,207.99 5.9,183.19 122.6,183.19 "/>
<polyline class="st4" points="831.51,207.99 5.5,207.99 5.5,183.19 831.51,183.19 "/>
<polyline class="st1" points="186.3,207.69 122.5,207.69 122.5,182.89 186.3,182.89 "/>
<line class="st4" x1="122.5" y1="161.79" x2="122.5" y2="222.99"/>
<line class="st4" x1="186" y1="161.79" x2="186" y2="222.99"/>
<line class="st4" x1="576.8" y1="161.79" x2="576.8" y2="222.99"/>
<path class="st5" d="M831.51,207.99"/>
<line class="st6" x1="153.5" y1="183.49" x2="153.5" y2="207.19"/>
<line class="st4" x1="36.5" y1="183.49" x2="36.5" y2="207.19"/>
<line class="st4" x1="67.6" y1="183.49" x2="67.6" y2="207.19"/>
<line class="st6" x1="186" y1="183.49" x2="186" y2="207.99"/>
<line class="st4" x1="218.4" y1="183.49" x2="218.4" y2="207.19"/>
<line class="st4" x1="250.9" y1="183.49" x2="250.9" y2="207.19"/>
<line class="st4" x1="283.3" y1="183.49" x2="283.3" y2="207.19"/>
<line class="st4" x1="315.9" y1="183.49" x2="315.9" y2="207.19"/>
<line class="st4" x1="348.4" y1="183.49" x2="348.4" y2="207.19"/>
<line class="st4" x1="381.2" y1="183.49" x2="381.2" y2="207.19"/>
<line class="st4" x1="413.8" y1="183.49" x2="413.8" y2="207.19"/>
<line class="st4" x1="446.3" y1="183.49" x2="446.3" y2="207.19"/>
<line class="st4" x1="478.9" y1="183.49" x2="478.9" y2="207.19"/>
<line class="st6" x1="511.51" y1="183.49" x2="511.51" y2="207.19"/>
<line class="st4" x1="511.51" y1="183.49" x2="511.51" y2="207.19"/>
<line class="st6" x1="544.01" y1="183.49" x2="544.01" y2="207.19"/>
<line class="st4" x1="544.1" y1="183.49" x2="544.1" y2="207.19"/>
<line class="st8" x1="186" y1="208.19" x2="122.5" y2="207.99"/>
<line class="st8" x1="122.5" y1="183.19" x2="186" y2="183.29"/>
<path class="st5" d="M385.5,208.19"/>
<path class="st5" d="M385.5,184.49"/>
</g>
<g>
<text transform="matrix(1 0 0 1 127.5176 240.9952)" class="st1 st9 st10">buf</text>
</g>
<g>
<g>
<line class="st11" x1="122.2" y1="240.79" x2="122.2" y2="229.79"/>
<g>
<polygon class="st1" points="119.8,230.09 124.6,230.09 122.2,227.69 "/>
</g>
</g>
</g>
<g>
<text transform="matrix(1 0 0 1 189.2273 240.9947)" class="st1 st9 st10">len=2</text>
</g>
<g>
<g>
<line class="st11" x1="185.88" y1="240.79" x2="185.88" y2="229.79"/>
<g>
<polygon class="st1" points="183.48,230.09 188.38,230.09 185.88,227.69 "/>
</g>
</g>
</g>
<g>
<text transform="matrix(1 0 0 1 581.4624 240.9952)" class="st1 st9 st10">size=16</text>
</g>
<g>
<g>
<line class="st11" x1="576.9" y1="240.79" x2="576.9" y2="229.79"/>
<g>
<polygon class="st1" points="574.51,230.09 579.4,230.09 576.9,227.69 "/>
</g>
</g>
</g>
<g>
<path class="st12" d="M687.51,195.89c0,0.9-0.7,1.6-1.6,1.6s-1.6-0.7-1.6-1.6c0-0.9,0.7-1.6,1.6-1.6
S687.51,195.09,687.51,195.89z"/>
<path class="st12" d="M701.2,195.89c0,0.9-0.7,1.6-1.6,1.6s-1.6-0.7-1.6-1.6c0-0.9,0.7-1.6,1.6-1.6S701.2,195.09,701.2,195.89z
"/>
<path class="st12" d="M714.9,195.89c0,0.9-0.7,1.6-1.6,1.6c-0.9,0-1.6-0.7-1.6-1.6c0-0.9,0.7-1.6,1.6-1.6
S714.9,195.09,714.9,195.89z"/>
<path class="st12" d="M728.7,195.89c0,0.9-0.7,1.6-1.6,1.6s-1.6-0.7-1.6-1.6c0-0.9,0.7-1.6,1.6-1.6
C728.01,194.29,728.7,195.09,728.7,195.89z"/>
</g>
<g>
<path class="st12" d="M82.8,195.89c0,0.9-0.7,1.6-1.6,1.6s-1.6-0.7-1.6-1.6c0-0.9,0.7-1.6,1.6-1.6S82.8,195.09,82.8,195.89z"/>
<path class="st12" d="M96.5,195.89c0,0.9-0.7,1.6-1.6,1.6s-1.6-0.7-1.6-1.6c0-0.9,0.7-1.6,1.6-1.6
C95.8,194.29,96.5,195.09,96.5,195.89z"/>
<path class="st12" d="M110.2,195.89c0,0.9-0.7,1.6-1.6,1.6s-1.6-0.7-1.6-1.6c0-0.9,0.7-1.6,1.6-1.6S110.2,195.09,110.2,195.89z
"/>
</g>
<g>
<g>
<text transform="matrix(1 0 0 1 133.6541 162.0062)" class="st13 st9 st10">data</text>
</g>
<g>
<text transform="matrix(1 0 0 1 327.363 162.0062)" class="st12 st9 st10">free space</text>
</g>
<g>
<g>
<line class="st14" x1="175.2" y1="168.79" x2="133.5" y2="168.79"/>
<g>
<polygon class="st13" points="174.9,171.39 176.5,168.79 174.9,166.29 174.8,166.29 174.8,171.39 "/>
</g>
<g>
<polygon class="st13" points="133.8,171.29 133.8,166.39 131.4,168.79 "/>
</g>
</g>
</g>
<g>
<g>
<line class="st15" x1="563.7" y1="168.79" x2="197.49" y2="168.79"/>
<g>
<polygon class="st12" points="563.4,171.29 563.4,166.39 565.8,168.79 "/>
</g>
<g>
<polygon class="st12" points="197.79,171.29 197.79,166.39 195.29,168.79 "/>
</g>
</g>
</g>
<g>
<text transform="matrix(1 0 0 1 615.4679 160.3837)" class="st12 st9 st10">non-allocated space</text>
</g>
<g>
<g>
<line class="st15" x1="593.7" y1="168.79" x2="829.7" y2="168.79"/>
<g>
<polygon class="st12" points="829.4,166.39 829.4,171.29 831.8,168.79 "/>
</g>
</g>
</g>
<g>
<g>
<line class="st15" x1="678.7" y1="168.79" x2="589.7" y2="168.79"/>
<g>
<polygon class="st12" points="590.01,171.29 590.01,166.39 587.6,168.79 "/>
</g>
</g>
</g>
</g>
<g>
<g>
<text transform="matrix(1 0 0 1 133.1212 200.1532)" class="st16 st9 st10">h</text>
</g>
</g>
<g>
<g>
<text transform="matrix(1 0 0 1 165.1565 200.1532)" class="st16 st9 st10">o</text>
</g>
</g>
</g>
</g>
</g>
<g>
<text transform="matrix(1 0 0 1 1.811981e-04 240.995)" class="st1 st9 st10">0</text>
</g>
<line class="st4" x1="5.5" y1="161.59" x2="5.5" y2="222.79"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1,384 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 858.5 424.3" style="enable-background:new 0 0 858.5 424.3;" xml:space="preserve">
<style type="text/css">
.st0{fill:#9E9B83;}
.st1{fill:#9E8900;}
.st2{font-family:Arial;font-weight:bold;}
.st3{font-size:11px;}
.st4{display:none;fill:#576174;}
.st5{fill:#1F5F75;}
.st6{clip-path:url(#SVGID_3_);fill:#FFFFFF;fill-opacity:0.1;}
.st7{fill:#277691;}
.st8{fill:#FFFFFF;}
.st9{font-family:Arial;font-weight:bold;}
.st10{fill:#004219;}
.st11{clip-path:url(#SVGID_6_);fill:#FFFFFF;fill-opacity:0.1;}
.st12{fill:#005721;}
.st13{fill:url(#SVGID_7_);}
.st14{clip-path:url(#XMLID_13_);fill:#FFFFFF;fill-opacity:0.1;}
.st15{fill:#752E06;}
.st16{fill:#963B08;}
.st17{fill:#59008F;}
.st18{clip-path:url(#XMLID_15_);fill:#FFFFFF;fill-opacity:0.1;}
.st19{fill:#7300B8;}
.st20{fill:#FFDE00;}
.st21{fill:#576174;}
.st22{font-size:13px;}
.st23{fill:#8A8C8E;}
.st24{font-size:12px;}
.st25{opacity:0.69;}
.st26{fill:#B3B1B3;}
.st27{clip-path:url(#XMLID_17_);fill:#FFFFFF;fill-opacity:0.1;}
.st28{fill:#919091;}
.st29{fill:#BFBEBF;}
.st30{fill:#454445;}
.st31{font-size:9px;}
.st32{clip-path:url(#XMLID_19_);fill:#FFFFFF;fill-opacity:0.1;}
.st33{fill:#8695B3;}
.st34{fill:url(#SVGID_8_);}
.st35{font-size:18px;}
.st36{clip-path:url(#XMLID_20_);fill:#610041;fill-opacity:0.1;}
.st37{fill:#610041;}
.st38{clip-path:url(#XMLID_22_);fill:url(#SVGID_9_);fill-opacity:0.1;}
.st39{fill:none;stroke:#86005B;stroke-width:0.5;stroke-linecap:round;stroke-miterlimit:10;}
.st40{fill:none;stroke:#86005B;stroke-width:0.75;stroke-linecap:round;stroke-miterlimit:10;}
.st41{fill:url(#SVGID_10_);}
.st42{fill:url(#SVGID_11_);}
.st43{fill:#610042;}
.st44{fill:url(#SVGID_12_);}
</style>
<g>
<g>
<path class="st0" d="M457.9,111.2l0.1,87.3c0,1.4-1.1,2.6-2.5,2.6h-76.3c-4.4-0.2-7.3-3.9-7.3-5.3l4.7-101.8
c0-1.4,0.8-7.4,2.2-7.4l54-0.1L457.9,111.2z"/>
</g>
<g>
<text transform="matrix(1 0 0 1 383.2604 149.9447)" class="st1 st2 st3">packed_fs.c</text>
</g>
</g>
<rect x="84" y="25" class="st4" width="727.7" height="429.8"/>
<g>
<g>
<path class="st5" d="M207.5,104.6v87.4c0,1.4-1.1,2.6-2.5,2.6h-78.3c-1.4,0-2.5-1.1-2.5-2.6V84.5c0-1.4,1.1-2.5,2.5-2.5h58
L207.5,104.6z"/>
<g>
<defs>
<path id="SVGID_1_" d="M205.5,102.6v87.4c0,1.4-1.1,2.6-2.5,2.6h-78.3c-1.4,0-2.5-1.1-2.5-2.6v-57.5V82.5c0-1.4,1.1-2.5,2.5-2.5
h58L205.5,102.6z"/>
</defs>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="122.1091" y1="136.2824" x2="205.5191" y2="136.2824">
<stop offset="0" style="stop-color:#45CFFF"/>
<stop offset="1" style="stop-color:#85DDFA"/>
</linearGradient>
<use xlink:href="#SVGID_1_" style="overflow:visible;fill:url(#SVGID_2_);"/>
<clipPath id="SVGID_3_">
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
</clipPath>
<path class="st6" d="M121.4,101.4c9.4,0.7,36.6,1.5,53.2,14.7c25,20,31.2,47.1,31.2,47.1l0.4,35.3l-84.8,0.2V101.4z"/>
</g>
<path class="st5" d="M205.9,104.4l-20.6,0c-1.4,0-2.6-1.2-2.6-2.6l0-21.8l22.9,22.7L205.9,104.4z"/>
<path class="st7" d="M205.7,102.8h-20.5c-1.4,0-2.5-1.1-2.5-2.5V79.9l0,0l22.9,22.7L205.7,102.8z"/>
<g>
<text transform="matrix(1 0 0 1 129.0689 92.8531)" class="st8 st9 st3">filex.y</text>
</g>
</g>
<g>
<path class="st10" d="M191.4,121.6V209c0,1.4-1.1,2.6-2.5,2.6h-78.3c-1.4,0-2.5-1.1-2.5-2.6V101.4c0-1.4,1.1-2.5,2.5-2.5h58
L191.4,121.6z"/>
<g>
<defs>
<path id="SVGID_4_" d="M189.4,119.6V207c0,1.4-1.1,2.6-2.5,2.6h-78.3c-1.4,0-2.5-1.1-2.5-2.6v-57.5V99.4c0-1.4,1.1-2.5,2.5-2.5
h58L189.4,119.6z"/>
</defs>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="106.0386" y1="153.2384" x2="189.4486" y2="153.2384">
<stop offset="0" style="stop-color:#00983A"/>
<stop offset="1" style="stop-color:#3A985D"/>
</linearGradient>
<use xlink:href="#SVGID_4_" style="overflow:visible;fill:url(#SVGID_5_);"/>
<clipPath id="SVGID_6_">
<use xlink:href="#SVGID_4_" style="overflow:visible;"/>
</clipPath>
<path class="st11" d="M105.3,118.3c9.4,0.7,36.6,1.5,53.2,14.7c25,20,31.2,47.1,31.2,47.1l0.4,35.3l-84.8,0.2V118.3z"/>
</g>
<path class="st10" d="M189.8,121.3l-20.6,0c-1.4,0-2.6-1.2-2.6-2.6l0-21.8l22.9,22.7L189.8,121.3z"/>
<path class="st12" d="M189.6,119.7h-20.5c-1.4,0-2.5-1.1-2.5-2.5V96.9l0,0l22.9,22.7L189.6,119.7z"/>
<g>
<text transform="matrix(1 0 0 1 113.9984 109.809)" class="st8 st9 st3">main.js</text>
</g>
</g>
<g>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="93.2369" y1="173.2516" x2="176.6469" y2="173.2516">
<stop offset="0" style="stop-color:#EA5B0C"/>
<stop offset="1" style="stop-color:#EA671E"/>
</linearGradient>
<path class="st13" d="M176.6,139.6V227c0,1.4-1.1,2.6-2.5,2.6H95.8c-1.4,0-2.5-1.1-2.5-2.6V119.5c0-1.4,1.1-2.5,2.5-2.5h58
L176.6,139.6z"/>
<g>
<defs>
<path id="XMLID_2_" d="M174.6,137.6V225c0,1.4-1.1,2.6-2.5,2.6H93.8c-1.4,0-2.5-1.1-2.5-2.6v-57.5v-50.1c0-1.4,1.1-2.5,2.5-2.5
h58L174.6,137.6z"/>
</defs>
<linearGradient id="XMLID_12_" gradientUnits="userSpaceOnUse" x1="91.2369" y1="171.2516" x2="174.6469" y2="171.2516">
<stop offset="0" style="stop-color:#EA5B0C"/>
<stop offset="1" style="stop-color:#EA7B3D"/>
</linearGradient>
<use xlink:href="#XMLID_2_" style="overflow:visible;fill:url(#XMLID_12_);"/>
<clipPath id="XMLID_13_">
<use xlink:href="#XMLID_2_" style="overflow:visible;"/>
</clipPath>
<path class="st14" d="M90.5,136.3c9.4,0.7,36.6,1.5,53.2,14.7c25,20,31.2,47.1,31.2,47.1l0.4,35.3l-84.8,0.2V136.3z"/>
</g>
<path class="st15" d="M175,139.3l-20.6,0c-1.4,0-2.6-1.2-2.6-2.6l0-21.8l22.9,22.7L175,139.3z"/>
<path class="st16" d="M174.8,137.7h-20.5c-1.4,0-2.5-1.1-2.5-2.5v-20.3l0,0l22.9,22.7L174.8,137.7z"/>
<g>
<text transform="matrix(1 0 0 1 97.1967 127.8222)" class="st8 st9 st3">style.css</text>
</g>
</g>
<g>
<path class="st17" d="M164.6,157.7v87.4c0,1.4-1.1,2.6-2.5,2.6H83.8c-1.4,0-2.5-1.1-2.5-2.6V137.5c0-1.4,1.1-2.5,2.5-2.5h58
L164.6,157.7z"/>
<g>
<defs>
<path id="XMLID_1_" d="M162.6,155.7v87.4c0,1.4-1.1,2.6-2.5,2.6H81.8c-1.4,0-2.5-1.1-2.5-2.6v-57.5v-50.1c0-1.4,1.1-2.5,2.5-2.5
h58L162.6,155.7z"/>
</defs>
<linearGradient id="XMLID_14_" gradientUnits="userSpaceOnUse" x1="79.2009" y1="189.3131" x2="162.6108" y2="189.3131">
<stop offset="0" style="stop-color:#A000FF"/>
<stop offset="1" style="stop-color:#C96EFF"/>
</linearGradient>
<use xlink:href="#XMLID_1_" style="overflow:visible;fill:url(#XMLID_14_);"/>
<clipPath id="XMLID_15_">
<use xlink:href="#XMLID_1_" style="overflow:visible;"/>
</clipPath>
<path class="st18" d="M78.5,154.4c9.4,0.7,36.6,1.5,53.2,14.7c25,20,31.2,47.1,31.2,47.1l0.4,35.3l-84.8,0.2V154.4z"/>
</g>
<path class="st17" d="M163,157.4l-20.6,0c-1.4,0-2.6-1.2-2.6-2.6l0-21.8l22.9,22.7L163,157.4z"/>
<path class="st19" d="M162.8,155.8h-20.5c-1.4,0-2.5-1.1-2.5-2.5V133l0,0l22.9,22.7L162.8,155.8z"/>
<g>
<text transform="matrix(1 0 0 1 83.1607 145.8838)" class="st8 st9 st3">index.html</text>
</g>
</g>
</g>
<g>
<g>
<path class="st20" d="M455.2,108.2v87.4c0,1.4-1.1,2.6-2.5,2.6h-78.3c-1.4,0-2.5-1.1-2.5-2.6V88.1c0-1.4,1.1-2.5,2.5-2.5h58
L455.2,108.2z"/>
<path class="st0" d="M455.3,110.3l-18.4,0c-1.4,0-2.5-1.1-2.5-2.5V87.5l0,0l20.9,20.7L455.3,110.3z"/>
<polygon class="st0" points="435.2,109.7 432.9,107.3 436.3,106.9 "/>
<path class="st1" d="M455.4,108.3h-20.5c-1.4,0-2.5-1.1-2.5-2.5V85.5l0,0l22.9,22.7L455.4,108.3z"/>
</g>
<g>
<text transform="matrix(1 0 0 1 379.2604 144.9447)" class="st21 st9 st22">packed_fs.c</text>
</g>
</g>
<g>
<polygon class="st23" points="483.6,195.4 397.3,195.4 394.3,193.4 397.3,162.8 481.5,159.7 483.6,162.8 "/>
<rect x="394.3" y="159.8" class="st21" width="87.3" height="33.7"/>
<g>
<g>
<text transform="matrix(1 0 0 1 400.0845 171.7588)" class="st8 st9 st24">mg_unpack()</text>
</g>
<g>
<text transform="matrix(1 0 0 1 400.0845 186.935)" class="st8 st9 st24">mg_unlist()</text>
</g>
</g>
</g>
<g class="st25">
<g>
<path class="st26" d="M467.7,256.6V344c0,1.4-1.1,2.6-2.5,2.6h-78.3c-1.4,0-2.5-1.1-2.5-2.6V236.4c0-1.4,1.1-2.5,2.5-2.5h58
L467.7,256.6z"/>
<g>
<defs>
<path id="XMLID_9_" d="M465.7,254.6V342c0,1.4-1.1,2.6-2.5,2.6h-78.3c-1.4,0-2.5-1.1-2.5-2.6v-57.5v-50.1c0-1.4,1.1-2.5,2.5-2.5
h58L465.7,254.6z"/>
</defs>
<linearGradient id="XMLID_16_" gradientUnits="userSpaceOnUse" x1="382.3115" y1="288.2073" x2="465.7215" y2="288.2073">
<stop offset="0" style="stop-color:#D6D4D6"/>
<stop offset="1" style="stop-color:#FFFDFE"/>
</linearGradient>
<use xlink:href="#XMLID_9_" style="overflow:visible;fill:url(#XMLID_16_);"/>
<clipPath id="XMLID_17_">
<use xlink:href="#XMLID_9_" style="overflow:visible;"/>
</clipPath>
<path class="st27" d="M381.6,253.3c9.4,0.7,36.6,1.5,53.2,14.7c25,20,31.2,47.1,31.2,47.1l0.4,35.3l-84.8,0.2V253.3z"/>
</g>
<path class="st28" d="M466.1,256.3l-20.6,0c-1.4,0-2.6-1.2-2.6-2.6l0-21.8l22.9,22.7L466.1,256.3z"/>
<path class="st29" d="M465.9,254.7h-20.5c-1.4,0-2.5-1.1-2.5-2.5v-20.3l0,0l22.9,22.7L465.9,254.7z"/>
<text transform="matrix(1 0 0 1 386.2713 244.7779)" class="st30 st9 st31">mongoose.c</text>
</g>
<g>
<path class="st26" d="M455.7,276.6v87.4c0,1.4-1.1,2.6-2.5,2.6h-78.3c-1.4,0-2.5-1.1-2.5-2.6V256.5c0-1.4,1.1-2.5,2.5-2.5h58
L455.7,276.6z"/>
<g>
<defs>
<path id="XMLID_8_" d="M453.7,274.6v87.4c0,1.4-1.1,2.6-2.5,2.6h-78.3c-1.4,0-2.5-1.1-2.5-2.6v-57.5v-50.1
c0-1.4,1.1-2.5,2.5-2.5h58L453.7,274.6z"/>
</defs>
<linearGradient id="XMLID_18_" gradientUnits="userSpaceOnUse" x1="370.2755" y1="308.2689" x2="453.6855" y2="308.2689">
<stop offset="0" style="stop-color:#D6D4D6"/>
<stop offset="1" style="stop-color:#FFFDFE"/>
</linearGradient>
<use xlink:href="#XMLID_8_" style="overflow:visible;fill:url(#XMLID_18_);"/>
<clipPath id="XMLID_19_">
<use xlink:href="#XMLID_8_" style="overflow:visible;"/>
</clipPath>
<path class="st32" d="M369.6,273.3c9.4,0.7,36.6,1.5,53.2,14.7c25,20,31.2,47.1,31.2,47.1l0.4,35.3l-84.8,0.2V273.3z"/>
<use xlink:href="#XMLID_8_" style="overflow:visible;fill:none;stroke:#FFFDFE;stroke-width:0.25;stroke-miterlimit:10;"/>
</g>
<path class="st28" d="M454,276.4l-20.6,0c-1.4,0-2.6-1.2-2.6-2.6l0-21.8l22.9,22.7L454,276.4z"/>
<path class="st29" d="M453.8,274.8h-20.5c-1.4,0-2.5-1.1-2.5-2.5v-20.3l0,0l22.9,22.7L453.8,274.8z"/>
<text transform="matrix(1 0 0 1 379.2353 264.8395)" class="st30 st9 st31">app.c</text>
</g>
</g>
<g>
<polygon class="st33" points="342.3,123.3 351.1,141.7 342.3,160.7 234.8,160.9 234.8,122.6 "/>
<path class="st23" d="M347.4,145.4"/>
<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="233.6667" y1="139.4088" x2="350.0318" y2="139.4088">
<stop offset="0" style="stop-color:#C1D5FF"/>
<stop offset="0.1995" style="stop-color:#BACDF6"/>
<stop offset="0.6001" style="stop-color:#B0C3EA"/>
<stop offset="1" style="stop-color:#ADC0E6"/>
</linearGradient>
<polygon class="st34" points="341.2,120.9 350,139.3 341.2,158.3 233.7,158.5 233.7,120.3 "/>
<text transform="matrix(1 0 0 1 262.9869 146.2633)" class="st8 st9 st35">PACK</text>
<polygon class="st33" points="234.8,160.9 233.7,158.5 238,158.7 "/>
</g>
<g>
<path class="st23" d="M634.9,144.6L634.9,144.6z"/>
</g>
<g>
<polygon class="st33" points="619.6,139.3 619.6,139.3 618.7,137.4 618.7,137.4 "/>
<path class="st23" d="M623.1,168.2L623.1,168.2z"/>
</g>
<g>
<polygon class="st33" points="619.6,288.1 618.7,290 618.7,290 619.6,288.1 "/>
<path class="st23" d="M623.1,259.2L623.1,259.2z"/>
</g>
<g>
<path class="st23" d="M617,282L617,282z"/>
</g>
<g>
<g>
<g>
<defs>
<path id="XMLID_10_" d="M722.7,276.5h-71.2c-11.6,0-21.1-9.4-21.1-21.1v-71.2c0-11.6,9.4-21.1,21.1-21.1h71.2
c11.6,0,21.1,9.4,21.1,21.1v71.2C743.8,267,734.4,276.5,722.7,276.5z"/>
</defs>
<use xlink:href="#XMLID_10_" style="overflow:visible;fill:#610041;"/>
<clipPath id="XMLID_20_">
<use xlink:href="#XMLID_10_" style="overflow:visible;"/>
</clipPath>
<path class="st36" d="M754.3,172.5c-2.7,8-17.3,30.9-65.8,30.8c-24.5,0-48.4-7.4-65.7-24.7l-12.1-12l69.3-64.6L754.3,172.5z"/>
</g>
<polygon class="st37" points="637.6,271.2 632.5,266.1 640.3,269.4 "/>
<polygon class="st37" points="738.3,169.9 734.6,166.2 736.4,171 "/>
<g>
<defs>
<path id="XMLID_5_" d="M719.7,273.5h-71.2c-11.6,0-21.1-9.4-21.1-21.1v-71.2c0-11.6,9.4-21.1,21.1-21.1h71.2
c11.6,0,21.1,9.4,21.1,21.1v71.2C740.8,264,731.4,273.5,719.7,273.5z"/>
</defs>
<radialGradient id="XMLID_21_" cx="684.119" cy="216.7761" r="56.6929" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#8B005D;stop-opacity:0.7"/>
<stop offset="1" style="stop-color:#8B005D"/>
</radialGradient>
<use xlink:href="#XMLID_5_" style="overflow:visible;fill:url(#XMLID_21_);"/>
<clipPath id="XMLID_22_">
<use xlink:href="#XMLID_5_" style="overflow:visible;"/>
</clipPath>
<radialGradient id="SVGID_9_" cx="678.486" cy="148.5836" r="90.2195" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#8B005D"/>
<stop offset="1" style="stop-color:#FFFFFF"/>
</radialGradient>
<path class="st38" d="M750.3,168.5c-2.7,8-17.3,30.9-65.8,30.8c-24.5,0-48.4-7.4-65.7-24.7l-12.1-12L676,97.9L750.3,168.5z"/>
</g>
<g>
<g>
<g>
<g>
<text transform="matrix(1 0 0 1 646.3768 216.49)" class="st8 st9 st35">APP.EXE</text>
</g>
</g>
</g>
</g>
</g>
<g>
<g>
<g>
<path class="st8" d="M732,240.1c-0.3-1.4-0.8-2.7-1.4-3.9l1.7-2.3c-0.3-0.5-0.6-0.9-1-1.4c-0.3-0.5-0.7-0.9-1.1-1.3l-2.7,1
c-1-1-2.2-1.8-3.4-2.4l0-2.9c-1-0.4-2.1-0.8-3.2-1l-1.5,2.4c-1.4-0.2-2.8-0.2-4.1,0l-1.7-2.4c-1.1,0.2-2.1,0.6-3.1,1.1l0.2,2.9
c-0.6,0.3-1.2,0.7-1.7,1.1c-0.6,0.4-1.1,0.9-1.5,1.4l-2.7-0.9l-1.9,2.7l1.8,2.3c-0.6,1.3-1,2.6-1.1,4l-2.7,0.9
c-0.1,1.1,0,2.2,0.1,3.3l2.8,0.8c0.3,1.3,0.8,2.7,1.4,3.9l-1.7,2.3c0.3,0.5,0.6,0.9,1,1.4c0.3,0.4,0.7,0.9,1.1,1.3l2.7-1
c1,1,2.2,1.8,3.4,2.4l0,2.9c1,0.4,2.1,0.8,3.2,1l1.5-2.4c1.4,0.2,2.8,0.2,4.1,0l1.7,2.4c1.1-0.3,2.1-0.6,3.1-1.1l-0.2-2.9
c0.6-0.3,1.2-0.7,1.7-1.1c0.6-0.4,1.1-0.9,1.5-1.4l2.8,0.9c0.7-0.8,1.3-1.8,1.9-2.7L731,249c0.6-1.3,1-2.6,1.1-4l2.7-0.9
c0.1-1.1,0-2.2-0.1-3.3L732,240.1z M724,251c-4.6,3.5-11.1,2.6-14.6-2c-3.5-4.6-2.6-11.1,2-14.6s11.1-2.6,14.6,2
C729.5,241,728.6,247.5,724,251z"/>
</g>
</g>
<g>
<g>
<path class="st8" d="M700.5,249.3c-0.2-0.8-0.5-1.6-0.8-2.4l1-1.4c-0.2-0.3-0.4-0.6-0.6-0.8c-0.2-0.3-0.4-0.5-0.7-0.8l-1.6,0.6
c-0.6-0.6-1.3-1.1-2.1-1.4l0-1.7c-0.6-0.3-1.3-0.5-1.9-0.6l-0.9,1.4c-0.8-0.1-1.7-0.1-2.5,0l-1-1.4c-0.6,0.1-1.3,0.4-1.9,0.6
l0.1,1.7c-0.4,0.2-0.7,0.4-1,0.7c-0.3,0.3-0.6,0.5-0.9,0.8l-1.6-0.6l-1.1,1.6l1.1,1.4c-0.3,0.8-0.6,1.6-0.7,2.4l-1.6,0.5
c0,0.7,0,1.3,0.1,2l1.7,0.5c0.2,0.8,0.4,1.6,0.9,2.4l-1,1.4c0.2,0.3,0.4,0.6,0.6,0.8c0.2,0.3,0.4,0.5,0.7,0.8l1.6-0.6
c0.6,0.6,1.3,1.1,2.1,1.4l0,1.7c0.6,0.3,1.3,0.5,1.9,0.6l0.9-1.4c0.8,0.1,1.7,0.1,2.5,0l1,1.4c0.6-0.2,1.3-0.4,1.9-0.6l-0.1-1.7
c0.4-0.2,0.7-0.4,1-0.7c0.3-0.3,0.6-0.5,0.9-0.8l1.6,0.6c0.4-0.5,0.8-1.1,1.1-1.6l-1.1-1.4c0.3-0.8,0.6-1.6,0.7-2.4l1.6-0.5
c0-0.7,0-1.3-0.1-2L700.5,249.3z M694.5,254.2c-1.8,1.4-4.5,1-5.9-0.8c-1.4-1.8-1-4.5,0.8-5.9c1.8-1.4,4.5-1,5.9,0.8
C696.8,250.2,696.4,252.8,694.5,254.2z"/>
</g>
</g>
<path class="st39" d="M708.9,234.5"/>
<path class="st39" d="M706.7,237.9"/>
<path class="st40" d="M705.7,242.7c0-1.7,0.4-3.4,1-4.9"/>
<path class="st40" d="M708.9,234.5c2.2-2.4,5.3-3.8,8.8-3.8"/>
<path class="st40" d="M687.4,247.3c1.1-1.4,2.7-2.2,4.6-2.2"/>
<path class="st39" d="M686.4,249.5"/>
<path class="st40" d="M686.2,250.9c0-0.5,0.1-1,0.2-1.4"/>
<path class="st39" d="M686.7,253.2"/>
</g>
</g>
<g>
<path class="st33" d="M611.7,174.2l-23,3.3l-13.1-9.6c-6.3-4.6-13.9-7.1-21.7-7l-55.5,0.1l-1.1-2.4h1.1l64.2-0.2h0.1l3.9,0h0.1
l3.2,2.4l19.2,14l0.2,0.2l23.4-3.3L611.7,174.2z"/>
<path class="st23" d="M595.9,145.5L595.9,145.5z"/>
<linearGradient id="SVGID_10_" gradientUnits="userSpaceOnUse" x1="497.2089" y1="147.636" x2="612.739" y2="147.636">
<stop offset="0" style="stop-color:#C1D5FF"/>
<stop offset="0.1995" style="stop-color:#BACDF6"/>
<stop offset="0.6001" style="stop-color:#B0C3EA"/>
<stop offset="1" style="stop-color:#ADC0E6"/>
</linearGradient>
<path class="st41" d="M612.7,171.7l-3,0.4l-20.3,2.8l-0.2-0.1l-13.6-9.9l0,0c-3.7-2.7-8.7-4.4-12.9-5.4c-2.9-0.7-6-1-9-1l-35.5,0.1
l-19.8,0.1h-1.1v-38.3l13.5,0.2l4,0l42.6,0.2l7.9,0c7,0,14,1.6,20.2,4.9c0,0,0,0,0,0c0.8,0.5,1.6,0.9,2.4,1.5l7.5,5.6l0.1,0.1l0,0
l15.4,11.3v0l0.1,3.2L612.7,171.7z"/>
<linearGradient id="SVGID_11_" gradientUnits="userSpaceOnUse" x1="597.6228" y1="138.4046" x2="598.5728" y2="138.4046">
<stop offset="0" style="stop-color:#C1D5FF"/>
<stop offset="0.1995" style="stop-color:#BACDF6"/>
<stop offset="0.6001" style="stop-color:#B0C3EA"/>
<stop offset="1" style="stop-color:#ADC0E6"/>
</linearGradient>
<polygon class="st42" points="598.6,139.3 598.6,139.4 597.6,137.4 597.7,137.5 "/>
<text transform="matrix(1 0 0 1 513.7678 146.3103)" class="st8 st9 st35">BUILD</text>
</g>
<g>
<path class="st33" d="M612.7,258.8l-0.1,2.2l-1.8-0.2l-20.3-2.8l-0.2,0.1l-13.6,9.9l0-0.1c-3.7,2.7-8.7,4.4-12.9,5.4
c-2.9,0.7-6,1-9,1l-35.5-0.1l-19.8-0.1h-1.1v35.4l-1.1,0.4l1.1,2.4l13.5-0.2l4,0l42.6-0.2l7.9-0.1c7,0,14-1.6,20.2-4.9c0,0,0,0,0,0
c0.8-0.5,1.6-0.9,2.4-1.5l7.5-5.6l0.1-0.1l0,0l15.4-11.3l1-0.9l1.9-26.7L612.7,258.8z"/>
<path class="st43" d="M593.5,272.5"/>
<g>
<path class="st23" d="M598.5,285L598.5,285z"/>
<linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="497.2089" y1="451.0054" x2="612.739" y2="451.0054" gradientTransform="matrix(1 0 0 -1 0 733.8616)">
<stop offset="0" style="stop-color:#C1D5FF"/>
<stop offset="0.1995" style="stop-color:#BACDF6"/>
<stop offset="0.6001" style="stop-color:#B0C3EA"/>
<stop offset="1" style="stop-color:#ADC0E6"/>
</linearGradient>
<path class="st44" d="M612.7,258.8l-3-0.4l-20.3-2.8l-0.2,0.1l-13.6,9.9l0,0c-3.7,2.7-8.7,4.4-12.9,5.4c-2.9,0.7-6,1-9,1
l-35.5-0.1l-19.8-0.1h-1.1v38.3l13.5-0.2l4,0l42.6-0.2l7.9,0c7,0,14-1.6,20.2-4.9c0,0,0,0,0,0c0.8-0.5,1.6-0.9,2.4-1.5l7.5-5.6
l0.1-0.1l0,0l15.4-11.3v0l0.1-3.2L612.7,258.8z"/>
<text transform="matrix(1 0 0 1 513.7682 298.0848)" class="st8 st9 st35">BUILD</text>
</g>
</g>
<path class="st33" d="M619.9,149.1"/>
</svg>

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1 @@
See detailed tutorial at https://mongoose.ws/tutorials/captive-dns-portal/

View File

@@ -0,0 +1,58 @@
// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
//
// Example captive DNS server (captive portal).
// 1. Run `make`. This builds and starts a server on port 5533
// 2. Run `dig -t A www.google.com -4 @localhost -p 5533`
#include "mongoose.h"
static const char *s_listen_url = "udp://0.0.0.0:5533";
// DNS answer section. We response with IP 1.2.3.4 - you can change it
// in the last 4 bytes of this array
uint8_t answer[] = {
0xc0, 0x0c, // Point to the name in the DNS question
0, 1, // 2 bytes - record type, A
0, 1, // 2 bytes - address class, INET
0, 0, 0, 120, // 4 bytes - TTL
0, 4, // 2 bytes - address length
1, 2, 3, 4 // 4 bytes - IP address
};
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_OPEN) {
c->is_hexdumping = 1;
} else if (ev == MG_EV_READ) {
struct mg_dns_rr rr; // Parse first question, offset 12 is header size
size_t n = mg_dns_parse_rr(c->recv.buf, c->recv.len, 12, true, &rr);
MG_INFO(("DNS request parsed, result=%d", (int) n));
if (n > 0) {
char buf[512];
struct mg_dns_header *h = (struct mg_dns_header *) buf;
memset(buf, 0, sizeof(buf)); // Clear the whole datagram
h->txnid = ((struct mg_dns_header *) c->recv.buf)->txnid; // Copy tnxid
h->num_questions = mg_htons(1); // We use only the 1st question
h->num_answers = mg_htons(1); // And only one answer
h->flags = mg_htons(0x8400); // Authoritative response
memcpy(buf + sizeof(*h), c->recv.buf + sizeof(*h), n); // Copy question
memcpy(buf + sizeof(*h) + n, answer, sizeof(answer)); // And answer
mg_send(c, buf, 12 + n + sizeof(answer)); // And send it!
}
mg_iobuf_del(&c->recv, 0, c->recv.len);
}
(void) fn_data;
(void) ev_data;
}
int main(void) {
struct mg_mgr mgr;
mg_log_set(MG_LL_DEBUG); // Set log level
mg_mgr_init(&mgr); // Initialise event manager
mg_listen(&mgr, s_listen_url, fn, NULL); // Start DNS server
for (;;) mg_mgr_poll(&mgr, 1000); // Event loop
mg_mgr_free(&mgr);
return 0;
}

View File

@@ -0,0 +1,27 @@
# A complete device dashboard
This example is a demonstration of how Mongoose Library could be integrated
into an embedded device and provide a complete device dashboard with the
following features:
- Authentication: login-protected dashboard
- Multiple logins with different permissions (admin and user)
- Web UI is fully embedded into the server/firmware binary, and does not
need a filesystem to serve it. UI is resilient to FS problems
- Administrators can change server settings
- All changes are propagates to all connected clients
- A device is connected to the external MQTT server
- Logged in clients can send/receive messages to a device which get
forwarded to MQTT
# Screenshots
This is a login screen that prompts for user/password
![](screenshots/login.png)
# Main dashboard
A main dashboard page shows and interactive MQTT console
![](screenshots/dashboard.png)

View File

@@ -0,0 +1 @@
../../test/data/ca.pem

View File

@@ -0,0 +1,18 @@
// Copyright (c) 2020-2022 Cesanta Software Limited
// All rights reserved
#include "mongoose.h"
const char *s_listening_url = "http://0.0.0.0:8000";
void device_dashboard_fn(struct mg_connection *, int, void *, void *);
int main(void) {
struct mg_mgr mgr;
mg_log_set(MG_LL_DEBUG); // Set debug log level
mg_mgr_init(&mgr);
mg_http_listen(&mgr, s_listening_url, device_dashboard_fn, &mgr);
MG_INFO(("Listening on %s", s_listening_url));
while (mgr.conns != NULL) mg_mgr_poll(&mgr, 500);
mg_mgr_free(&mgr);
return 0;
}

View File

@@ -0,0 +1,217 @@
// Copyright (c) 2020-2022 Cesanta Software Limited
// All rights reserved
#include "mongoose.h"
#define MQTT_SERVER "mqtt://broker.hivemq.com:1883"
#define MQTT_PUBLISH_TOPIC "mg/my_device"
#define MQTT_SUBSCRIBE_TOPIC "mg/#"
static time_t s_boot_timestamp = 0; // Updated by SNTP
static struct mg_connection *s_sntp_conn = NULL; // SNTP connection
// Define system time()
time_t time(time_t *tp) {
time_t t = s_boot_timestamp + (time_t) (mg_millis() / 1000);
if (tp != NULL) *tp = t;
return t;
}
// Authenticated user.
// A user can be authenticated by:
// - a name:pass pair
// - a token
// When a user is shown a login screen, she enters a user:pass. If successful,
// a server returns user info which includes token. From that point on,
// client can use token for authentication. Tokens could be refreshed/changed
// on a server side, forcing clients to re-login.
struct user {
const char *name, *pass, *token;
};
// This is a configuration structure we're going to show on a dashboard
static struct config {
char *url, *pub, *sub; // MQTT settings
} s_config;
static struct mg_connection *s_mqtt = NULL; // MQTT connection
static bool s_connected = false; // MQTT connection established
// Try to update a single configuration value
static void update_config(struct mg_str *body, const char *name, char **value) {
char buf[256];
if (mg_http_get_var(body, name, buf, sizeof(buf)) > 0) {
free(*value);
*value = strdup(buf);
}
}
// Parse HTTP requests, return authenticated user or NULL
static struct user *getuser(struct mg_http_message *hm) {
// In production, make passwords strong and tokens randomly generated
// In this example, user list is kept in RAM. In production, it can
// be backed by file, database, or some other method.
static struct user users[] = {
{"admin", "pass0", "admin_token"},
{"user1", "pass1", "user1_token"},
{"user2", "pass2", "user2_token"},
{NULL, NULL, NULL},
};
char user[256], pass[256];
struct user *u;
mg_http_creds(hm, user, sizeof(user), pass, sizeof(pass));
if (user[0] != '\0' && pass[0] != '\0') {
// Both user and password is set, search by user/password
for (u = users; u->name != NULL; u++)
if (strcmp(user, u->name) == 0 && strcmp(pass, u->pass) == 0) return u;
} else if (user[0] == '\0') {
// Only password is set, search by token
for (u = users; u->name != NULL; u++)
if (strcmp(pass, u->token) == 0) return u;
}
return NULL;
}
// Notify all config watchers about the config change
static void send_notification(struct mg_mgr *mgr, const char *fmt, ...) {
struct mg_connection *c;
for (c = mgr->conns; c != NULL; c = c->next) {
if (c->label[0] == 'W') {
va_list ap;
va_start(ap, fmt);
mg_ws_vprintf(c, WEBSOCKET_OP_TEXT, fmt, &ap);
va_end(ap);
}
}
}
// Send simulated metrics data to the dashboard, for chart rendering
static void timer_metrics_fn(void *param) {
send_notification(param, "{%Q:%Q,%Q:[%lu, %d]}", "name", "metrics", "data",
(unsigned long) time(NULL),
10 + (int) ((double) rand() * 10 / RAND_MAX));
}
// MQTT event handler function
static void mqtt_fn(struct mg_connection *c, int ev, void *ev_data, void *fnd) {
if (ev == MG_EV_CONNECT && mg_url_is_ssl(s_config.url)) {
struct mg_tls_opts opts = {.ca = "ca.pem",
.srvname = mg_url_host(s_config.url)};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_MQTT_OPEN) {
s_connected = true;
c->is_hexdumping = 1;
mg_mqtt_sub(s_mqtt, mg_str(s_config.sub), 2);
send_notification(c->mgr, "{%Q:%Q,%Q:null}", "name", "config", "data");
} else if (ev == MG_EV_MQTT_MSG) {
struct mg_mqtt_message *mm = ev_data;
send_notification(c->mgr, "{%Q:%Q,%Q:{%Q: %.*Q, %Q: %.*Q, %Q: %d}}", "name",
"message", "data", "topic", (int) mm->topic.len,
mm->topic.ptr, "data", (int) mm->data.len, mm->data.ptr,
"qos", (int) mm->qos);
} else if (ev == MG_EV_MQTT_CMD) {
struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data;
MG_DEBUG(("cmd %d qos %d", mm->cmd, mm->qos));
} else if (ev == MG_EV_CLOSE) {
s_mqtt = NULL;
if (s_connected) {
s_connected = false;
send_notification(c->mgr, "{%Q:%Q,%Q:null}", "name", "config", "data");
}
}
(void) fnd;
}
// Keep MQTT connection open - reconnect if closed
static void timer_mqtt_fn(void *param) {
struct mg_mgr *mgr = (struct mg_mgr *) param;
if (s_mqtt == NULL) {
struct mg_mqtt_opts opts = {0};
s_mqtt = mg_mqtt_connect(mgr, s_config.url, &opts, mqtt_fn, NULL);
}
}
// SNTP connection event handler. When we get a response from an SNTP server,
// adjust s_boot_timestamp. We'll get a valid time from that point on
static void sfn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_SNTP_TIME) {
uint64_t t = *(uint64_t *) ev_data;
MG_INFO(("%lu SNTP: %lld ms from epoch", c->id, t));
s_boot_timestamp = (time_t) ((t - mg_millis()) / 1000);
c->is_closing = 1;
} else if (ev == MG_EV_CLOSE) {
s_sntp_conn = NULL;
}
(void) fn_data;
}
static void timer_sntp_fn(void *param) { // SNTP timer function. Sync up time
struct mg_mgr *mgr = (struct mg_mgr *) param;
if (s_sntp_conn == NULL && s_boot_timestamp == 0) {
s_sntp_conn = mg_sntp_connect(mgr, NULL, sfn, NULL);
}
}
// HTTP request handler function
void device_dashboard_fn(struct mg_connection *c, int ev, void *ev_data,
void *fn_data) {
if (ev == MG_EV_OPEN && c->is_listening) {
mg_timer_add(c->mgr, 1000, MG_TIMER_REPEAT, timer_metrics_fn, c->mgr);
mg_timer_add(c->mgr, 1000, MG_TIMER_REPEAT, timer_mqtt_fn, c->mgr);
mg_timer_add(c->mgr, 1000, MG_TIMER_REPEAT, timer_sntp_fn, c->mgr);
s_config.url = strdup(MQTT_SERVER);
s_config.pub = strdup(MQTT_PUBLISH_TOPIC);
s_config.sub = strdup(MQTT_SUBSCRIBE_TOPIC);
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
struct user *u = getuser(hm);
// MG_INFO(("%p [%.*s] auth %s", c->fd, (int) hm->uri.len, hm->uri.ptr,
// u ? u->name : "NULL"));
if (mg_http_match_uri(hm, "/api/hi")) {
mg_http_reply(c, 200, "", "hi\n"); // Testing endpoint
} else if (u == NULL && mg_http_match_uri(hm, "/api/#")) {
// All URIs starting with /api/ must be authenticated
mg_http_reply(c, 403, "", "Denied\n");
} else if (mg_http_match_uri(hm, "/api/config/get")) {
mg_http_reply(c, 200, NULL, "{%Q:%Q,%Q:%Q,%Q:%Q,%Q:%s}\n", "url",
s_config.url, "pub", s_config.pub, "sub", s_config.sub,
"connected", s_connected ? "true" : "false");
} else if (mg_http_match_uri(hm, "/api/config/set")) {
// Admins only
if (strcmp(u->name, "admin") == 0) {
update_config(&hm->body, "url", &s_config.url);
update_config(&hm->body, "pub", &s_config.pub);
update_config(&hm->body, "sub", &s_config.sub);
if (s_mqtt) s_mqtt->is_closing = 1; // Ask to disconnect from MQTT
send_notification(fn_data, "{%Q:%Q,%Q:null}", "name", "config", "data");
mg_http_reply(c, 200, "", "ok\n");
} else {
mg_http_reply(c, 403, "", "Denied\n");
}
} else if (mg_http_match_uri(hm, "/api/message/send")) {
char buf[256];
if (s_connected &&
mg_http_get_var(&hm->body, "message", buf, sizeof(buf)) > 0) {
mg_mqtt_pub(s_mqtt, mg_str(s_config.pub), mg_str(buf), 1, false);
}
mg_http_reply(c, 200, "", "ok\n");
} else if (mg_http_match_uri(hm, "/api/watch")) {
c->label[0] = 'W'; // Mark ourselves as a event listener
mg_ws_upgrade(c, hm, NULL);
} else if (mg_http_match_uri(hm, "/api/login")) {
mg_http_reply(c, 200, NULL, "{%Q:%Q,%Q:%Q}\n", "user", u->name, "token",
u->token);
} else {
struct mg_http_serve_opts opts = {0};
#if 1
opts.root_dir = "/web_root";
opts.fs = &mg_fs_packed;
#else
opts.root_dir = "web_root";
#endif
mg_http_serve_dir(c, ev_data, &opts);
}
MG_DEBUG(("%.*s %.*s -> %.*s", (int) hm->method.len, hm->method.ptr,
(int) hm->uri.len, hm->uri.ptr, (int) 3, &c->send.buf[9]));
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Device Dashboard</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="style.css" />
</head>
<body></body>
<script type="module" src="main.js"></script>
</html>

View File

@@ -0,0 +1,378 @@
'use strict';
import {Component, h, html, render, useEffect, useState, useRef} from './preact.min.js';
const MaxMetricsDataPoints = 50;
// This simple publish/subscribe is used to pass notifications that were
// received from the server, to all child components of the app.
var PubSub = (function() {
var handlers = {}, id = 0;
return {
subscribe: function(fn) {
handlers[id++] = fn;
},
unsubscribe: function(id) {
delete handlers[id];
},
publish: function(data) {
for (var k in handlers) handlers[k](data);
}
};
})();
const Nav = props => html`
<div style="background: #333; padding: 0.5em; color: #fff;">
<div class="container d-flex">
<div style="flex: 1 1 auto; display: flex; align-items: center;">
<b>Your Product</b>
</div>
<div style="display: flex; align-items: center; flex: 0 0 auto; ">
<span>Logged in as:</span>
<span style="padding: 0 0.5em;"><img src="user.png" height="22" /></span>
<span>${props.user}</span>
<a class="btn" onclick=${props.logout}
style="margin-left: 1em; font-size: 0.8em; background: #8aa;">logout</a>
</div>
</div>
</div>`;
const Hero = props => html`
<div class="section">
<div style="margin-top: 1em; background: #eee; padding: 1em; border-radius: 0.5em; color: #777; ">
<h1 style="margin: 0.2em 0;">Interactive Device Dashboard</h1>
<p>
This device dashboard is developed using the modern and compact Preact framework,
in order to fit on very small devices. This is
a <a href="https://mongoose.ws/tutorials/http-server/">hybrid server</a> which
provides both static and dynamic content. Static files, like CSS/JS/HTML
or images, are compiled into the server binary.
This UI uses the REST API implemented by the device, which you can examine
using <code>curl</code> command-line utility:
</p>
<div><code>curl -u admin:pass0 localhost:8000/api/config/get</code> </div>
<div><code>curl -u admin:pass0 localhost:8000/api/config/set -d 'pub=mg/topic'</code> </div>
<div><code>curl -u admin:pass0 localhost:8000/api/message/send -d 'message=hello'</code> </div>
<p>
The device can send notifications to this dashboard at anytime. Notifications
are sent over WebSocket at URI <code>/api/watch</code> as JSON strings: <code>{"name": "..", "data": ...}</code>
<div>Try <code>wscat --auth user1:pass1 --connect ws://localhost:8000/api/watch</code></div>
</p>
</div>
</div>`;
const Login = function(props) {
const [user, setUser] = useState('');
const [pass, setPass] = useState('');
const login = ev =>
fetch(
'/api/login',
{headers: {Authorization: 'Basic ' + btoa(user + ':' + pass)}})
.then(r => r.json())
.then(r => r && props.login(r))
.catch(err => err);
return html`
<div class="rounded border" style="max-width: 480px; margin: 0 auto; margin-top: 5em; background: #eee; ">
<div style="padding: 2em; ">
<h1 style="color: #666;">Device Dashboard Login </h1>
<div style="margin: 0.5em 0;">
<input type='text' placeholder='Name' style="width: 100%;"
oninput=${ev => setUser(ev.target.value)} value=${user} />
</div>
<div style="margin: 0.5em 0;">
<input type="password" placeholder="Password" style="width: 100%;"
oninput=${ev => setPass(ev.target.value)} value=${pass}
onchange=${login} />
</div>
<div style="margin: 1em 0;">
<button class="btn" style="width: 100%; background: #8aa;"
disabled=${!user || !pass} onclick=${login}> Login </button>
</div>
<div style="color: #777; margin-top: 2em;">
Valid logins: admin:pass0, user1:pass1, user2:pass2
</div>
</div>
</div>`;
};
const Configuration = function(props) {
const [url, setUrl] = useState(props.config.url || '');
const [pub, setPub] = useState(props.config.pub || '');
const [sub, setSub] = useState(props.config.sub || '');
useEffect(() => {
setUrl(props.config.url);
setPub(props.config.pub);
setSub(props.config.sub);
}, [props.config]);
const update = (name, val) => fetch('/api/config/set', {
method: 'post',
body: `${name}=${encodeURIComponent(val)}`
}).catch(err => err);
const updateurl = ev => update('url', url);
const updatepub = ev => update('pub', pub);
const updatesub = ev => update('sub', sub);
// console.log(props, [url, pub, sub]);
return html`
<div class="section">
<h3 style="background: #c03434; color: #fff; padding: 0.4em;">
Device Configuration</h3>
<div style="margin: 0.5em 0; display: flex;">
<span class="addon nowrap">MQTT server:</span>
<input type="text" style="flex: 1 100%;"
value=${url} onchange=${updateurl}
oninput=${ev => setUrl(ev.target.value)} />
<button class="btn" disabled=${!url} onclick=${updateurl}
style="margin-left: 1em; background: #8aa;">Update</button>
</div>
<div style="margin: 0.5em 0; display: flex; ">
<span class="addon nowrap">Subscribe topic:</span>
<input type="text" style="flex: 1 100%;"
value=${sub} onchange=${updatesub}
oninput=${ev => setSub(ev.target.value)} />
<button class="btn" disabled=${!sub} onclick=${updatesub}
style="margin-left: 1em; background: #8aa;">Update</button>
</div>
<div style="margin: 0.5em 0; display: flex;">
<span class="addon nowrap">Publish topic:</span>
<input type="text" style="flex: 1 100%;"
value=${pub} onchange=${updatepub}
oninput=${ev => setPub(ev.target.value)} />
<button class="btn" disabled=${!pub} onclick=${updatepub}
style="margin-left: 1em; background: #8aa;">Update</button>
</div>
<div>
You can use <a href="http://www.hivemq.com/demos/websocket-client/">
HiveMQ Websocket web client</a> to send messages to this console.
</div>
<div class="msg">
The device keeps a persistent connection to the configured MQTT server.
Changes to this configuration are propagated to all dashboards: try
changing them in this dashboard and observe changes in other opened
dashboards.
</div><div class="msg">
Note: administrators can see this section and can change device
configuration, whilst users cannot.
</div>
</div>`;
};
const Message = m => html`<div style="margin: 0.5em 0;">
<span class="qos">qos: ${m.message.qos} </span>
<span class="topic">topic: ${m.message.topic} </span>
<span class="data">data: ${m.message.data}</span>
</div>`;
const Messages = function(props) {
const [messages, setMessages] = useState([]);
const [txt, setTxt] = useState('');
useEffect(() => {
const id = PubSub.subscribe(function(msg) {
if (msg.name == 'message') setMessages(x => x.concat([msg.data]));
});
return PubSub.unsubscribe(id);
}, []);
const sendmessage = ev => fetch('/api/message/send', {
method: 'post',
body: `message=${encodeURIComponent(txt)}`
}).then(r => setTxt(''));
const connstatus = props.config.connected ? 'connected' : 'disconnected';
return html`
<div class="section">
<h3 style="background: #30c040; color: #fff; padding: 0.4em;">MQTT messages</h3>
<div>
MQTT server status: <b>${connstatus}</b>
</div>
<div style="height: 10em; overflow: auto; padding: 0.5em; " class="border">
${messages.map(message => h(Message, {message}))}
</div>
<div style="margin: 0.5em 0; display: flex">
<span class="addon nowrap">Publish message:</span>
<input placeholder="type and press enter..." style="flex: 1 100%;"
value=${txt} onchange=${sendmessage}
oninput=${ev => setTxt(ev.target.value)} />
</div>
<div class="msg">
The message gets passed to the device via REST. Then the device sends it to
the MQTT server over MQTT. All MQTT messages on a subscribed topic
received by the device, are propagated to this dashboard via /api/watch.
</div>
</div>`;
};
// Expected arguments:
// data: timeseries, e.g. [ [1654361352, 19], [1654361353, 18], ... ]
// width, height, yticks, xticks, ymin, ymax, xmin, xmax
const SVG = function(props) {
// w
// +---------------------+
// | h1 |
// | +-----------+ |
// | | | | h
// | w1 | | w2 |
// | +-----------+ |
// | h2 |
// +---------------------+
//
let w = props.width, h = props.height, w1 = 30, w2 = 0, h1 = 8, h2 = 18;
let yticks = props.yticks || 4, xticks = props.xticks || 5;
let data = props.data || [];
let ymin = props.ymin || 0;
let ymax = props.ymax || Math.max.apply(null, data.map(p => p[1]));
let xmin = props.xmin || Math.min.apply(null, data.map(p => p[0]));
let xmax = props.xmax || Math.max.apply(null, data.map(p => p[0]));
// Y-axis tick lines and labels
let yta = (new Array(yticks + 1)).fill(0).map((_, i) => i); // indices
let yti = i => h - h2 - (h - h1 - h2) * i / yticks; // index's Y
let ytv = i => (ymax - ymin) * i / yticks;
let ytl = y => html`<line x1=${w1} y1=${y} x2=${w} y2=${y} class="tick"/>`;
let ytt = (y, v) => html`<text x=0 y=${y + 5} class="label">${v}</text>`;
// X-axis tick lines and labels
let datefmt = unix => (new Date(unix * 1000)).toISOString().substr(14, 5);
let xta = (new Array(xticks + 1)).fill(0).map((_, i) => i); // indices
let xti = i => w1 + (w - w1 - w2) * i / xticks; // index's X
let xtv = i => datefmt(xmin + (xmax - xmin) * i / xticks);
let xtl = x => html`<path d="M ${x},${h1} L ${x},${h - h2}" class="tick"/>`;
let xtt = (x, v) =>
html`<text x=${x - 15} y=${h - 2} class="label">${v}</text>`;
// Transform data points array into coordinate
let dx = v => w1 + (v - xmin) / ((xmax - xmin) || 1) * (w - w1 - w2);
let dy = v => h - h2 - (v - ymin) / ((ymax - ymin) || 1) * (h - h1 - h2);
let dd = data.map(p => [Math.round(dx(p[0])), Math.round(dy(p[1]))]);
let ddl = dd.length;
// And plot the data as <path> element
let begin0 = ddl ? `M ${dd[0][0]},${dd[0][1]}` : `M 0,0`;
let begin = `M ${w1},${h - h2}`; // Initial point
let end = ddl ? `L ${dd[ddl - 1][0]},${h - h2}` : `L ${w1},${h - h2}`;
let series = ddl ? dd.map(p => `L ${p[0]} ${p[1]}`) : [];
return html`
<svg viewBox="0 0 ${w} ${h}">
<style>
.axis { stroke: #aaa; fill: none; }
.label { stroke: #aaa; font-size: 13px; }
.tick { stroke: #ccc; fill: none; stroke-dasharray: 5; }
.seriesbg { stroke: none; fill: rgba(200,225,255, 0.25)}
.series { stroke: #25a; fill: none; }
</style>
${yta.map(i => ytl(yti(i)))}
${yta.map(i => ytt(yti(i), ytv(i)))}
${xta.map(i => xtl(xti(i)))}
${data.length ? xta.map(i => xtt(xti(i), xtv(i))) : ''}
<path d="${begin} ${series.join(' ')} ${end}" class="seriesbg" />
<path d="${begin0} ${series.join(' ')}" class="series" />
</svg>`;
};
const Chart = function(props) {
const [data, setData] = useState([]);
useEffect(() => {
const id = PubSub.subscribe(function(msg) {
if (msg.name != 'metrics') return;
setData(x => x.concat([msg.data]).splice(-MaxMetricsDataPoints));
});
return PubSub.unsubscribe(id);
}, []);
let xmax = 0, missing = MaxMetricsDataPoints - data.length;
if (missing > 0) xmax = Math.round(Date.now() / 1000) + missing;
return html`
<div class="section">
<h3 style="background: #ec3; color: #fff; padding: 0.4em;">Data Chart</h3>
<div style="overflow: auto; padding: 0.5em;" class="">
<${SVG} height=240 width=600 ymin=0 ymax=20 xmax=${xmax} data=${data} />
</div>
<div class="msg">
This chart plots live sensor data, sent by the device via /api/watch.
</div>
</div>`;
};
const App = function(props) {
const [user, setUser] = useState('');
const [config, setConfig] = useState({});
const getconfig = () =>
fetch('/api/config/get', {headers: {Authorization: ''}})
.then(r => r.json())
.then(r => setConfig(r))
.catch(err => console.log(err));
// Watch for notifications. As soon as a notification arrives, pass it on
// to all subscribed components
const watch = function() {
var l = window.location, proto = l.protocol.replace('http', 'ws');
var tid, wsURI = proto + '//' + l.host + '/api/watch'
var reconnect = function() {
var ws = new WebSocket(wsURI);
// ws.onopen = () => console.log('ws connected');
ws.onmessage = function(ev) {
try {
var msg = JSON.parse(ev.data);
PubSub.publish(msg);
// if (msg.name != 'metrics') console.log('ws->', msg);
} catch (e) {
console.log('Invalid ws frame:', ev.data); // eslint-disable-line
}
};
ws.onclose = function() {
clearTimeout(tid);
tid = setTimeout(reconnect, 1000);
console.log('ws disconnected');
};
};
reconnect();
};
const login = function(u) {
document.cookie = `access_token=${u.token}; Secure, HttpOnly; SameSite=Lax; path=/; max-age=3600`;
setUser(u.user);
watch();
return getconfig();
};
const logout = ev => {
document.cookie = `access_token=; Secure, HttpOnly; SameSite=Lax; path=/; max-age=0`;
setUser('');
};
useEffect(() => {
// Called once at init time
PubSub.subscribe(msg => msg.name == 'config' && getconfig());
fetch('/api/login', {headers: {Authorization: ''}})
.then(r => r.json())
.then(r => login(r))
.catch(err => setUser(''));
}, []);
if (!user) return html`<${Login} login=${login} />`;
return html`
<${Nav} user=${user} logout=${logout} />
<div class="container">
<div class="row">
<div class="col col-6"><${Hero} /></div>
<div class="col col-6"><${Chart} /></div>
<div class="col col-6">
${user == 'admin' && h(Configuration, {config})}
</div>
<div class="col col-6"><${Messages} config=${config} /></div>
</div>
</div>`;
};
window.onload = () => render(h(App), document.body);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,43 @@
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; font: 16px sans-serif; }
select, input, label::before, textarea { outline: none; box-shadow:none !important; border: 1px solid #ccc !important; }
code, pre { color: #373; font-family: monospace; font-weight: bolder; font-size: smaller; background: #ddd; padding: 0.1em 0.3em; border-radius: 0.2em; }
textarea, input, .addon { font-size: 15px; border: 1px solid #ccc; padding: 0.5em; }
a, a:visited, a:active { color: #55f; }
.addon { background: #eee; min-width: 9em;}
.btn {
background: #ccc; border-radius: 0.3em; border: 0; color: #fff; cursor: pointer;
display: inline-block; padding: 0.6em 2em; font-weight: bolder;
}
.btn[disabled] { opacity: 0.5; cursor: auto;}
.smooth { transition: all .2s; }
.container { margin: 0 20px; width: auto; }
.d-flex { display: flex; }
.d-none { display: none; }
.border { border: 1px solid #ddd; }
.rounded { border-radius: 0.5em; }
.nowrap { white-space: nowrap; }
.msg { background: #def; border-left: 5px solid #59d; padding: 0.5em; font-size: 90%; margin: 1em 0; }
.section { margin: 0 1em; }
.topic, .data, .qos { padding: 0.2em 0.5em; border-radius: 0.4em; margin-right: 0.5em; }
.qos { background: #efa; }
.topic { background: #fea; }
.data { background: #aef; }
/* Grid */
.row { display: flex; flex-wrap: wrap; }
.col { margin: 0; padding: 0; overflow: auto; }
.col-12 { width: 100%; }
.col-11 { width: 91.66%; }
.col-10 { width: 83.33%; }
.col-9 { width: 75%; }
.col-8 { width: 66.66%; }
.col-7 { width: 58.33%; }
.col-6 { width: 50%; }
.col-5 { width: 41.66%; }
.col-4 { width: 33.33%; }
.col-3 { width: 25%; }
.col-2 { width: 16.66%; }
.col-1 { width: 8.33%; }
@media (min-width: 1310px) { .container { margin: auto; width: 1270px; } }
@media (max-width: 920px) { .row .col { width: 100%; } }

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,28 @@
// Copyright (c) 2022 Cesanta Software Limited
// All rights reserved
#include "mongoose.h"
const char *s_listening_url = "http://0.0.0.0:8000";
// HTTP request handler function
void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_serve_opts opts = {
.root_dir = "/web_root",
.fs = &mg_fs_packed
};
mg_http_serve_dir(c, ev_data, &opts);
}
(void) fn_data;
}
int main(void) {
struct mg_mgr mgr;
mg_log_set(MG_LL_INFO);
mg_mgr_init(&mgr);
mg_http_listen(&mgr, s_listening_url, fn, NULL);
while (true) mg_mgr_poll(&mgr, 500);
mg_mgr_free(&mgr);
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="description" content="Mongoose Embedded Filesytem example" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Mongoose Embedded Filesytem example</title>
<link rel="stylesheet" href="style.css" />
</head>
<body></body>
<script type="module" src="main.js"></script>
</html>

View File

@@ -0,0 +1,88 @@
'use strict';
import { h, html, render } from './preact.min.js';
const X = function () {
return html`
<div class="container">
<h2 class="section">ABOUT US</h2>
<div class="row">
<div class="col-7">
<p>
Cesanta Software Ltd. is headquartered in Dublin, Republic of Ireland.
</p>
<p></p>
Our story roots back to 2004, when Mongoose Web Server Library development started. <br>
As Mongoose Web Server grew in popularity and matured over the following years, in 2013 Cesanta was established to continue its development and
provide support to our valued customers.
</p>
<p>We are proud to have among our customers many <i>Fortune 500</i> companies as well as medium and small size businesses.
Security and quality of our solutions is a paramount for us and the fact that Mongoose Web Server is used by NASA aboard the International Space Station is
the best confirmation to it.</p>
<p>Since 2013, Cesanta has expanded its product portfolio. We develop and distribute embedded software and hardware with focus on connected products and the Internet of Things.</p>
</div>
</div>
</div>
`;
};
const Y = function () {
return html`
<div class="container">
<h3 class="section">Among our products are:</h3>
<div class="row">
<div class="col-7">
<ul>
<li>
<a href="https://mongoose.ws/">Mongoose Web Server</a>
- an embedded web server and networking library
</li>
<li>
<a href="https://vcon.io/">VCON.io</a>
- Arduino-compatible boards with built-in firmware OTA updates
and management dashboard
</li>
<li>
<a href="https://mdash.net/">mDash.net</a>
- an all-in-one IoT Platform
</li>
<li>
<a href="https://mongoose-os.com">Mongoose OS</a>
- an operating system for low-power microcontrollers
</li>
<li>
<a href="https://github.com/cesanta/mjs">mJS</a>
- an embedded JavaScript engine for C/C++
</li>
</ul>
</div>
<div class="col-6">
<p><b>Our solutions are:</b></p>
<ul>
<li>integrated into thousands of commercial products</li>
<li>deployed to hundreds of millions devices in production environments</li>
</ul>
</div>
</div>
</div>
`;
};
const App = function (props) {
return html`
<h1>Basic Embedded Filesystem demo</h1>
<div>
${h(X)}
</div>
<div>
${h(Y)}
</div>`;
};
window.onload = () => render(h(App), document.body);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,43 @@
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; font: 16px sans-serif; }
select, input, label::before, textarea { outline: none; box-shadow:none !important; border: 1px solid #ccc !important; }
code, pre { color: #373; font-family: monospace; font-weight: bolder; font-size: smaller; background: #ddd; padding: 0.1em 0.3em; border-radius: 0.2em; }
textarea, input, .addon { font-size: 15px; border: 1px solid #ccc; padding: 0.5em; }
a, a:visited, a:active { color: #55f; }
.addon { background: #eee; min-width: 9em;}
.btn {
background: #ccc; border-radius: 0.3em; border: 0; color: #fff; cursor: pointer;
display: inline-block; padding: 0.6em 2em; font-weight: bolder;
}
.btn[disabled] { opacity: 0.5; cursor: auto;}
.smooth { transition: all .2s; }
.container { margin: 0 20px; width: auto; }
.d-flex { display: flex; }
.d-none { display: none; }
.border { border: 1px solid #ddd; }
.rounded { border-radius: 0.5em; }
.nowrap { white-space: nowrap; }
.msg { background: #def; border-left: 5px solid #59d; padding: 0.5em; font-size: 90%; margin: 1em 0; }
.section { margin: 0 1em; }
.topic, .data, .qos { padding: 0.2em 0.5em; border-radius: 0.4em; margin-right: 0.5em; }
.qos { background: #efa; }
.topic { background: #fea; }
.data { background: #aef; }
/* Grid */
.row { display: flex; flex-wrap: wrap; }
.col { margin: 0; padding: 0; overflow: auto; }
.col-12 { width: 100%; }
.col-11 { width: 91.66%; }
.col-10 { width: 83.33%; }
.col-9 { width: 75%; }
.col-8 { width: 66.66%; }
.col-7 { width: 58.33%; }
.col-6 { width: 50%; }
.col-5 { width: 41.66%; }
.col-4 { width: 33.33%; }
.col-3 { width: 25%; }
.col-2 { width: 16.66%; }
.col-1 { width: 8.33%; }
@media (min-width: 1310px) { .container { margin: auto; width: 1270px; } }
@media (max-width: 920px) { .row .col { width: 100%; } }

View File

@@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(mongoose-esp32-example)

View File

@@ -0,0 +1 @@
See detailed tutorial at https://mongoose.ws/tutorials/esp32/device-dashboard

View File

@@ -0,0 +1,7 @@
idf_component_register(SRCS "main.c"
"wifi.c"
"net.c"
"packed_fs.c"
"mongoose.c")
component_compile_options(-DMG_ENABLE_LINES)
component_compile_options(-DMG_ENABLE_PACKED_FS)

View File

@@ -0,0 +1,35 @@
// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
#include "esp_spiffs.h"
//#include "freertos/FreeRTOS.h"
#include "mongoose.h"
const char *s_listening_url = "http://0.0.0.0:80";
void device_dashboard_fn(struct mg_connection *, int, void *, void *);
#define WIFI_SSID "YOUR_WIFI_NETWORK_NAME" // SET THIS!
#define WIFI_PASS "YOUR_WIFI_PASSWORD" // SET THIS!
#define FS_ROOT "/spiffs"
void app_main(void) {
// Mount filesystem
esp_vfs_spiffs_conf_t conf = {
.base_path = FS_ROOT, .max_files = 20, .format_if_mount_failed = true};
int res = esp_vfs_spiffs_register(&conf);
MG_INFO(("FS %s, %d", conf.base_path, res));
mg_file_printf(&mg_fs_posix, FS_ROOT "/hello.txt", "%s", "hello from ESP");
// Setup wifi. This function is implemented in wifi.c
// It blocks until connected to the configured WiFi network
void wifi_init(const char *ssid, const char *pass);
wifi_init(WIFI_SSID, WIFI_PASS);
// Connected to WiFi, now start HTTP server
struct mg_mgr mgr;
mg_log_set(MG_LL_DEBUG); // Set log level
mg_mgr_init(&mgr);
MG_INFO(("Mongoose v%s on %s", MG_VERSION, s_listening_url));
mg_http_listen(&mgr, s_listening_url, device_dashboard_fn, &mgr);
for (;;) mg_mgr_poll(&mgr, 1000); // Infinite event loop
}

View File

@@ -0,0 +1 @@
../../../../mongoose.c

View File

@@ -0,0 +1 @@
../../../../mongoose.h

View File

@@ -0,0 +1 @@
../../../device-dashboard/net.c

View File

@@ -0,0 +1 @@
../../../device-dashboard/packed_fs.c

View File

@@ -0,0 +1,104 @@
// Code taken from the ESP32 IDF WiFi station Example
#include <string.h>
#include "esp_event.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "mongoose.h"
static EventGroupHandle_t s_wifi_event_group;
/* The event group allows multiple bits for each event, but we only care about
* two events:
* - we are connected to the AP with an IP
* - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static int s_retry_num = 0;
static void event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT &&
event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < 3) {
esp_wifi_connect();
s_retry_num++;
MG_INFO(("retry to connect to the AP"));
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
MG_ERROR(("connect to the AP fail"));
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
MG_INFO(("IP ADDRESS:" IPSTR, IP2STR(&event->ip_info.ip)));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
void wifi_init(const char *ssid, const char *pass) {
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(
WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(
IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip));
wifi_config_t c = {.sta = {.threshold = {.authmode = WIFI_AUTH_WPA2_PSK},
.pmf_cfg = {.capable = true, .required = false}}};
snprintf((char *) c.sta.ssid, sizeof(c.sta.ssid), "%s", ssid);
snprintf((char *) c.sta.password, sizeof(c.sta.password), "%s", pass);
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &c));
ESP_ERROR_CHECK(esp_wifi_start());
MG_DEBUG(("wifi_init_sta finished."));
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE, pdFALSE, portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT) {
MG_INFO(("connected to ap SSID:%s password:%s", ssid, pass));
} else if (bits & WIFI_FAIL_BIT) {
MG_ERROR(("Failed to connect to SSID:%s, password:%s", ssid, pass));
} else {
MG_ERROR(("UNEXPECTED EVENT"));
}
/* The event will not be processed after unregister */
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(
IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(
WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
vEventGroupDelete(s_wifi_event_group);
}

View File

@@ -0,0 +1,5 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
storage, data, spiffs, 0x10000, 0x10000,
factory, app, factory, 0x100000, 1M,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x6000
3 phy_init data phy 0xf000 0x1000
4 storage data spiffs 0x10000 0x10000
5 factory app factory 0x100000 1M

View File

@@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(mongoose-esp32-example)

View File

@@ -0,0 +1,61 @@
# A UART to network bridge for ESP32
This example is a demonstration of how Mongoose Library could be integrated
into an embedded device and provide a UART-to-Network bridge capability:
- A device opens listening TCP port and Websocket port and waits for connections
- When a client connects, data is exchanged with the device's UART
- Everything that client send, is sent to the UART
- Everything that is read from the UART, gets sent to the client
- Multiple clients are allowed
- Live UART console allows to talk to the UART from the web page
- Web UI is hardcoded into the binary and does not need a filesystem
# Screenshots
![](../../uart-bridge/screenshots/dashboard.png)
# Build and flash
Build requires Docker installed, and uses Espressif's ESP-IDF docker image:
```sh
make build
make flash PORT=/dev/YOURSERIAL
```
# Flash pre-built firmware
You can flash a pre-built firmware to the ESP32 device using the following
instructions:
1. Connect your ESP32 device to the workstation. It should be accessible
via a serial port
2. Download and unzip ESP32 flashing tool from https://mongoose.ws/downloads/esputil.zip
3. Download a prebuilt firmware https://mongoose.ws/downloads/uart-bridge.hex into the unzipped directory
4. Start command prompt (or terminal on Mac/Linux). Run `cd
PATH/TO/esputil` to go into the unzipped `esputil/` directory. After that, run
the following command (change `COMPORT` to the board's serial port):
| OS | Command |
| ------- | ------- |
| Windows | <pre><code class="language-bash">.\windows\esputil -p COMPORT flash uart-bridge.hex</code></pre>|
| Linux | <pre><code class="language-bash">./linux/esputil -p COMPORT flash uart-bridge.hex</pre> |
| MacOS | <pre><code class="language-bash">./macos/esputil -p COMPORT flash uart-bridge.hex</code></pre> |
Next step is to monitor and follow the instructions.
```sh
esputil -p COMPORT monitor
```
Note: if monitor command shows constant restarts, the flash parameters
settings can be wrong. Reflash your device with `-fp ...` flash parameters
settings. For example, WROOM-32 based boards use `-fp 0x220`:
```sh
esputil -p COMPORT -fp 0x220 flash uart-bridge.hex
```
For more on possible options for flash parameters, see
https://github.com/cpq/esputil#flash-parameters

View File

@@ -0,0 +1,10 @@
idf_component_register(SRCS "main.c"
"wifi.c"
"uart.c"
"cli.c"
"net.c"
"packed_fs.c"
"mongoose.c")
component_compile_options(-DMG_ENABLE_LINES=1)
component_compile_options(-DMG_ENABLE_PACKED_FS=1)
component_compile_options(-DUART_API_IMPLEMENTED=1)

View File

@@ -0,0 +1,93 @@
#include "main.h"
static void cli_wifi(const char *ssid, const char *pass) {
if (wifi_init(ssid, pass)) {
mg_file_printf(&mg_fs_posix, WIFI_FILE, "{%Q:%Q,%Q:%Q}\n", "ssid", ssid,
"pass", pass);
MG_INFO(("Reboot now"));
}
}
static void cli_ls(void) {
DIR *dirp = opendir(FS_ROOT);
struct dirent *dp;
if (dirp == NULL) {
MG_ERROR(("Cannot open FS: %d", errno));
} else {
while ((dp = readdir(dirp)) != NULL) {
/* Do not show current and parent dirs */
if (strcmp((const char *) dp->d_name, ".") == 0 ||
strcmp((const char *) dp->d_name, "..") == 0) {
continue;
} else {
printf("%s\n", dp->d_name);
}
}
closedir(dirp);
}
}
static void cli_cat(const char *fname) {
char path[MG_PATH_MAX];
snprintf(path, sizeof(path), "%s/%s", FS_ROOT, fname);
FILE *fp = fopen(path, "r");
if (fp != NULL) {
int ch;
while ((ch = fgetc(fp)) != EOF) putchar(ch);
fclose(fp);
}
}
static void cli_rm(const char *fname) {
char path[100];
snprintf(path, sizeof(path), "%s/%s", FS_ROOT, fname);
remove(path);
}
static struct mg_iobuf in;
void cli(uint8_t input_byte) {
if (input_byte == 0 || input_byte == 0xff) return;
if (in.len >= 128) in.len = 0;
mg_iobuf_add(&in, in.len, &input_byte, sizeof(input_byte));
if (input_byte == '\n') {
const char *arrow = "---";
char buf0[10], buf1[50], buf2[250];
in.buf[in.len] = '\0';
buf0[0] = buf1[0] = buf2[0] = '\0';
sscanf((char *) in.buf, "%9s %49s %249[^\r\n]", buf0, buf1, buf2);
printf("%s CLI command: '%s'\n", arrow, buf0);
if (strcmp(buf0, "reboot") == 0) {
esp_restart();
} else if (strcmp(buf0, "ls") == 0) {
cli_ls();
} else if (strcmp(buf0, "cat") == 0) {
cli_cat(buf1);
} else if (strcmp(buf0, "rm") == 0) {
cli_rm(buf1);
} else if (strcmp(buf0, "reboot") == 0) {
esp_restart();
} else if (strcmp(buf0, "ll") == 0) {
mg_log_set(atoi(buf1));
} else if (strcmp(buf0, "wifi") == 0) {
cli_wifi(buf1, buf2);
} else {
printf("%s %s\n", arrow, "Unknown command. Usage:");
printf("%s %s\n", arrow, " set NAME VALUE");
printf("%s %s\n", arrow, " rm FILENAME");
printf("%s %s\n", arrow, " cat FILENAME");
printf("%s %s\n", arrow, " ls");
printf("%s %s\n", arrow, " reboot");
printf("%s %s\n", arrow, " wifi WIFI_NET WIFI_PASS");
}
printf("%s %s\n", arrow, "CLI output end");
in.len = 0;
}
}
void cli_init(void) {
mg_iobuf_init(&in, 0, 32);
}

View File

@@ -0,0 +1,50 @@
// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
#include "main.h"
const char *s_listening_url = "http://0.0.0.0:80";
char *config_read(void) {
return mg_file_read(&mg_fs_posix, FS_ROOT "/config.json", NULL);
}
void config_write(struct mg_str config) {
mg_file_write(&mg_fs_posix, FS_ROOT "/config.json", config.ptr, config.len);
}
void app_main(void) {
// Mount filesystem
esp_vfs_spiffs_conf_t conf = {
.base_path = FS_ROOT, .max_files = 20, .format_if_mount_failed = true};
int res = esp_vfs_spiffs_register(&conf);
MG_INFO(("FS at %s initialised, status: %d", conf.base_path, res));
// Try to connect to wifi by using saved WiFi credentials
char *json = mg_file_read(&mg_fs_posix, WIFI_FILE, NULL);
if (json != NULL) {
char *ssid = mg_json_get_str(mg_str(json), "$.ssid");
char *pass = mg_json_get_str(mg_str(json), "$.pass");
while (!wifi_init(ssid, pass)) (void) 0;
free(ssid);
free(pass);
free(json);
} else {
// If WiFi is not configured, run CLI until configured
MG_INFO(("WiFi is not configured, running CLI. Press enter"));
cli_init();
for (;;) {
uint8_t ch = getchar();
cli(ch);
usleep(10000);
}
}
// Connected to WiFi, now start HTTP server
struct mg_mgr mgr;
mg_mgr_init(&mgr);
mg_log_set(MG_LL_DEBUG); // Set log level
MG_INFO(("Mongoose v%s on %s", MG_VERSION, s_listening_url));
mg_http_listen(&mgr, s_listening_url, uart_bridge_fn, &mgr);
for (;;) mg_mgr_poll(&mgr, 10); // Infinite event loop
}

View File

@@ -0,0 +1,18 @@
#pragma once
#include "mongoose.h"
#include "driver/gpio.h"
#include "driver/uart.h"
#include "esp_spiffs.h"
#include "freertos/FreeRTOS.h"
#define FS_ROOT "/spiffs"
#define WIFI_FILE FS_ROOT "/wifi.json"
#define UART_NO 1
void uart_bridge_fn(struct mg_connection *, int, void *, void *);
int uart_read(void *buf, size_t len);
bool wifi_init(const char *ssid, const char *pass);
void cli(uint8_t ch);
void cli_init(void);

View File

@@ -0,0 +1 @@
../../../../mongoose.c

View File

@@ -0,0 +1 @@
../../../../mongoose.h

View File

@@ -0,0 +1 @@
../../../uart-bridge/net.c

View File

@@ -0,0 +1 @@
../../../uart-bridge/packed_fs.c

View File

@@ -0,0 +1,44 @@
#include "main.h"
int uart_close(int no) {
return uart_driver_delete(no);
}
int uart_open(int no, int rx, int tx, int cts, int rts, int baud) {
uart_config_t uart_config = {
.baud_rate = baud,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = cts > 0 && rts > 0 ? UART_HW_FLOWCTRL_CTS_RTS
: cts > 0 ? UART_HW_FLOWCTRL_CTS
: rts > 0 ? UART_HW_FLOWCTRL_RTS
: UART_HW_FLOWCTRL_DISABLE,
};
int e1 = uart_param_config(no, &uart_config);
int e2 = uart_set_pin(no, tx, rx, rts, cts);
int e3 =
uart_driver_install(no, UART_FIFO_LEN * 2, UART_FIFO_LEN * 2, 0, NULL, 0);
MG_INFO(("%d: %d/%d/%d, %d %d %d", no, rx, tx, baud, e1, e2, e3));
if (e1 != ESP_OK || e2 != ESP_OK || e3 != ESP_OK) return -1;
return no;
}
void uart_init(int tx, int rx, int baud) {
uart_open(UART_NO, rx, tx, -1, -1, baud);
}
int uart_read(void *buf, size_t len) {
size_t x = 0;
int no = UART_NO;
if (uart_get_buffered_data_len(no, &x) != ESP_OK || x == 0) return 0;
int n = uart_read_bytes(no, buf, len, 10 / portTICK_PERIOD_MS);
MG_DEBUG(("%d bytes: [%.*s]", n, n, (char *) buf));
return n;
}
int uart_write(const void *buf, int len) {
int no = UART_NO;
MG_DEBUG(("%d bytes: [%.*s]", len, len, (char *) buf));
return uart_write_bytes(no, (const char *) buf, len);
}

View File

@@ -0,0 +1,108 @@
// Code taken from the ESP32 IDF WiFi station Example
#include <string.h>
#include "esp_event.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "mongoose.h"
static EventGroupHandle_t s_wifi_event_group;
/* The event group allows multiple bits for each event, but we only care about
* two events:
* - we are connected to the AP with an IP
* - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static int s_retry_num = 0;
static void event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT &&
event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < 3) {
esp_wifi_connect();
s_retry_num++;
MG_INFO(("retry to connect to the AP"));
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
MG_ERROR(("connect to the AP fail"));
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
MG_INFO(("IP ADDRESS: " IPSTR ". Go to:", IP2STR(&event->ip_info.ip)));
MG_INFO(("http://" IPSTR, IP2STR(&event->ip_info.ip)));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
bool wifi_init(const char *ssid, const char *pass) {
bool result = false;
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(
WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(
IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip));
wifi_config_t c = {.sta = {.threshold = {.authmode = WIFI_AUTH_WPA2_PSK},
.pmf_cfg = {.capable = true, .required = false}}};
snprintf((char *) c.sta.ssid, sizeof(c.sta.ssid), "%s", ssid);
snprintf((char *) c.sta.password, sizeof(c.sta.password), "%s", pass);
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &c));
ESP_ERROR_CHECK(esp_wifi_start());
MG_DEBUG(("wifi_init_sta finished."));
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE, pdFALSE, portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT) {
MG_INFO(("connected to ap SSID:%s", ssid));
result = true;
} else if (bits & WIFI_FAIL_BIT) {
MG_ERROR(("Failed to connect to SSID:%s, password:%s", ssid, pass));
} else {
MG_ERROR(("UNEXPECTED EVENT"));
}
/* The event will not be processed after unregister */
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(
IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(
WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
vEventGroupDelete(s_wifi_event_group);
return result;
}

View File

@@ -0,0 +1,5 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
storage, data, spiffs, 0x10000, 0x10000,
factory, app, factory, 0x100000, 1M,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x6000
3 phy_init data phy 0xf000 0x1000
4 storage data spiffs 0x10000 0x10000
5 factory app factory 0x100000 1M

View File

@@ -0,0 +1,22 @@
# An example ESP8266 application
To build this application, follow these steps:
1. Make sure you have Docker installed
2. Clone whole mongoose repository on your machine:
```sh
git clone https://github.com/cesanta/mongoose.git
```
3. Start command line or terminal, and go into the example's directory:
```sh
cd mongoose/examples/esp8266
```
4. Run `make`. This invokes a Docker-based build. A firmware will be built
in the `src/build/` directory:
```sh
make build
```
5. Flash your ESP8266. If you have esptool.py installed, the build process will end telling you the command to flash your device. You can just run `make` again to use Docker:
```sh
make flash PORT=/your/serial
```

View File

@@ -0,0 +1,10 @@
#
# Main component makefile.
#
# This Makefile can be left empty. By default, it will take the sources in the
# src/ directory, compile them and link them into lib(subdirectory_name).a
# in the build directory. This behaviour is entirely configurable,
# please read the ESP-IDF documents if you need to do this.
#
COMPONENT_INCLUDES += ../../../../..

View File

@@ -0,0 +1,54 @@
// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
#include "mongoose.h"
#define WIFI_SSID "WIFI_NETWORK" // SET THIS!
#define WIFI_PASS "WIFI_PASSWORD" // SET THIS!
#define SERVER_URL "http://0.0.0.0:80"
#define CLIENT_URL "http://info.cern.ch"
// Event handler for an server (accepted) connection
static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_HTTP_MSG) {
mg_http_reply(c, 200, "", "Hello from ESP!\n");
}
}
// Event handler for a client connection - fetch the first web page in history
// To enable TLS for HTTP,
// 1. Copy "ca.pem" file to the ESP8266 flash FS
// 2. Add TLS init snippet for the connection, see examples/http-client
static void cb2(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_CONNECT) {
struct mg_str s = mg_url_host(CLIENT_URL);
mg_printf(c, "GET / HTTP/1.0\r\nHost: %.*s\r\n\r\n", (int) s.len, s.ptr);
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = ev_data; // Print HTTP response
MG_INFO(("Fetched:\n%.*s", (int) hm->message.len, hm->message.ptr));
c->is_closing = 1;
}
}
// Called after we're connected to WiFi network
static void run_mongoose(void) {
struct mg_mgr mgr;
mg_log_set(MG_LL_DEBUG); // Set log level
mg_mgr_init(&mgr);
mg_http_listen(&mgr, SERVER_URL, cb, &mgr); // Listening server
mg_http_connect(&mgr, CLIENT_URL, cb2, &mgr); // Example client
MG_INFO(("Starting Mongoose web server v%s", MG_VERSION));
for (;;) mg_mgr_poll(&mgr, 1000);
mg_mgr_free(&mgr);
}
void app_main(void) {
// Setup wifi. This function is implemented in wifi.c
// It blocks until connected to the configured WiFi network
void wifi_init(const char *ssid, const char *pass);
wifi_init(WIFI_SSID, WIFI_PASS);
// Done connecting to WiFi, now start HTTP server
run_mongoose();
}

View File

@@ -0,0 +1 @@
../../../../mongoose.c

View File

@@ -0,0 +1,86 @@
#include <string.h>
#include "esp_event.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "mongoose.h"
static EventGroupHandle_t s_wifi_event_group;
/* The event group allows multiple bits for each event, but we only care about
* two events:
* - we are connected to the AP with an IP
* - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static int s_retry_num = 0;
static void event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT &&
event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < 3) {
esp_wifi_connect();
s_retry_num++;
MG_DEBUG(("retry to connect to the AP"));
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
MG_ERROR(("connect to the AP fail"));
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
MG_INFO(("got ip:%s", ip4addr_ntoa(&event->ip_info.ip)));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
void wifi_init(const char *ssid, const char *pass) {
s_wifi_event_group = xEventGroupCreate();
tcpip_adapter_init();
ESP_ERROR_CHECK(esp_event_loop_create_default());
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
&event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
&event_handler, NULL));
wifi_config_t c = {};
snprintf((char *) c.sta.ssid, sizeof(c.sta.ssid), "%s", ssid);
snprintf((char *) c.sta.password, sizeof(c.sta.password), "%s", pass);
if (pass != NULL && pass[0] != '\0') {
c.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
}
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &c));
ESP_ERROR_CHECK(esp_wifi_start());
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE, pdFALSE, portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT) {
MG_INFO(("connected to ap SSID:%s password:%s", ssid, pass));
} else if (bits & WIFI_FAIL_BIT) {
MG_ERROR(("Failed to connect to SSID:%s, password:%s", ssid, pass));
} else {
MG_ERROR(("UNEXPECTED EVENT"));
}
ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP,
&event_handler));
ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID,
&event_handler));
vEventGroupDelete(s_wifi_event_group);
}

View File

@@ -0,0 +1 @@
See detailed tutorial at https://mongoose.ws/tutorials/file-uploads/

View File

@@ -0,0 +1,51 @@
// Copyright (c) 2021 Cesanta Software Limited
// All rights reserved
#include "mongoose.h"
// HTTP request handler function. It implements the following endpoints:
// /upload - prints all submitted form elements
// all other URI - serves web_root/ directory
//
// ///////////////// IMPORTANT //////////////////////////
//
// Mongoose has a limit on input buffer, which also limits maximum upload size.
// It is controlled by the MG_MAX_RECV_SIZE constant, which is set by
// default to (3 * 1024 * 1024), i.e. 3 megabytes.
// Use -DMG_MAX_BUF_SIZE=NEW_LIMIT to override it.
//
// Also, consider changing -DMG_IO_SIZE=SOME_BIG_VALUE to increase IO buffer
// increment when reading data.
static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
MG_INFO(("New request to: [%.*s], body size: %lu", (int) hm->uri.len,
hm->uri.ptr, (unsigned long) hm->body.len));
if (mg_http_match_uri(hm, "/upload")) {
struct mg_http_part part;
size_t ofs = 0;
while ((ofs = mg_http_next_multipart(hm->body, ofs, &part)) > 0) {
MG_INFO(("Chunk name: [%.*s] filename: [%.*s] length: %lu bytes",
(int) part.name.len, part.name.ptr, (int) part.filename.len,
part.filename.ptr, (unsigned long) part.body.len));
}
mg_http_reply(c, 200, "", "Thank you!");
} else {
struct mg_http_serve_opts opts = {.root_dir = "web_root"};
mg_http_serve_dir(c, ev_data, &opts);
}
}
}
int main(void) {
struct mg_mgr mgr;
mg_mgr_init(&mgr);
mg_log_set(MG_LL_DEBUG); // Set log level
mg_http_listen(&mgr, "http://localhost:8000", cb, NULL);
for (;;) mg_mgr_poll(&mgr, 50);
mg_mgr_free(&mgr);
return 0;
}

View File

@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>example</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
#container { margin-right: auto; margin-left: auto; max-width: 480px; }
#info { background: #e0f0f0; border-radius: .5em; padding: 2em; }
#wrapper { margin-top: 1em; }
form * { margin: 0.2em 0; }
</style>
</head>
<body>
<div id="container">
<div id="info">
Mongoose always buffers a full HTTP message before invoking
MG_EV_HTTP_MSG event. Big POST request require of lot
of RAM to buffer everything. Therefore, standard form uploads
should be used only when Mongoose runs on a system with lots of RAM.
Otherwise, please see <code>file-updload</code> example, how
a big file could be uploaded to a device with little RAM.
<br/><br/>
In this example, a standard HTML form upload is used, which uses
<code>multipart-form-data</code> encoding with
several variables and file upload. On a server side, a
<code>mg_http_next_multipart()</code> API is used to iterate over
all submitted form elements and print their payload.
</div>
<div id="wrapper">
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="text" name="field1" value="type some text here" /> <br/>
<input type="file" name="file1" /> </br>
<button type="submit">submit form</button>
</form>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1 @@
See detailed tutorial at https://mongoose.ws/tutorials/file-uploads/

View File

@@ -0,0 +1,40 @@
// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
#include "mongoose.h"
// HTTP request handler function. It implements the following endpoints:
// /upload - Saves the next file chunk
// all other URI - serves web_root/ directory
static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_http_match_uri(hm, "/upload")) {
char path[80], name[64];
mg_http_get_var(&hm->query, "name", name, sizeof(name));
if (name[0] == '\0') {
mg_http_reply(c, 400, "", "%s", "name required");
} else {
mg_snprintf(path, sizeof(path), "/tmp/%s", name);
mg_http_upload(c, hm, &mg_fs_posix, mg_remove_double_dots(path), 99999);
}
} else {
struct mg_http_serve_opts opts = {.root_dir = "web_root"};
mg_http_serve_dir(c, ev_data, &opts);
}
}
}
int main(void) {
struct mg_mgr mgr;
struct mg_timer t1;
mg_mgr_init(&mgr);
mg_log_set(MG_LL_DEBUG); // Set log level
mg_http_listen(&mgr, "http://localhost:8000", cb, NULL);
for (;;) mg_mgr_poll(&mgr, 50);
mg_mgr_free(&mgr);
return 0;
}

View File

@@ -0,0 +1,48 @@
// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
// Helper function to display upload status
var setStatus = function(text) {
document.getElementById('el3').innerText = text;
};
// When user clicks on a button, trigger file selection dialog
var button = document.getElementById('el2');
button.onclick = function(ev) {
input.click();
};
// Send a large blob of data chunk by chunk
var sendFileData = function(name, data, chunkSize) {
var sendChunk = function(offset) {
var chunk = data.subarray(offset, offset + chunkSize) || '';
var opts = {method: 'POST', body: chunk};
var url = '/upload?offset=' + offset + '&name=' + encodeURIComponent(name);
var ok;
setStatus(
'Uploading ' + name + ', bytes ' + offset + '..' +
(offset + chunk.length) + ' of ' + data.length);
fetch(url, opts)
.then(function(res) {
if (res.ok && chunk.length > 0) sendChunk(offset + chunk.length);
ok = res.ok;
return res.text();
})
.then(function(text) {
if (!ok) setStatus('Error: ' + text);
});
};
sendChunk(0);
};
// If user selected a file, read it into memory and trigger sendFileData()
var input = document.getElementById('el1');
input.onchange = function(ev) {
if (!ev.target.files[0]) return;
var f = ev.target.files[0], r = new FileReader();
r.readAsArrayBuffer(f);
r.onload = function() {
ev.target.value = '';
sendFileData(f.name, new Uint8Array(r.result), 2048);
};
};

View File

@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>example</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
#container { margin-right: auto; margin-left: auto; max-width: 480px; }
#info { background: #e0f0f0; border-radius: .5em; padding: 2em; }
#wrapper { margin-top: 1em; }
</style>
</head>
<body>
<div id="container">
<div id="info">
Mongoose always buffers a full HTTP message before invoking
the MG_EV_HTTP_MSG event. A big POST request would require a lot
of RAM to buffer everything. Therefore, in order to upload large
files on memory-constrained systems, a large file should be sent
in small chunks.
<br/><br/>
In this example, the JavaScript code on this page sends the chosen
file in 2Kb chunks using the <code>/upload</code> endpoint.
The uploaded file is stored in the <code>/tmp</code> directory by
the helper API function <code>mg_http_upload()</code>
</div>
<div id="wrapper">
<input type="file" id="el1" style="display: none"/>
<button id="el2">choose file...</button>
<div id="el3"></div>
</div>
</div>
</body>
<script src="app.js"></script>
</html>

View File

@@ -0,0 +1 @@
See detailed tutorial at https://mongoose.ws/tutorials/file-uploads/

View File

@@ -0,0 +1,47 @@
// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
//
// Streaming upload example. Demonstrates how to use chunked encoding
// to send large payload in smaller chunks. To test, use curl utility:
//
// curl http://localhost:8000/upload?name=a.txt --data-binary @large_file.txt
#include "mongoose.h"
// HTTP request handler function. It implements the following endpoints:
// /upload - Saves the next file chunk
// all other URI - serves web_root/ directory
static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (ev == MG_EV_HTTP_CHUNK && mg_http_match_uri(hm, "/upload")) {
MG_INFO(("Got chunk len %lu", (unsigned long) hm->chunk.len));
MG_INFO(("Query string: [%.*s]", (int) hm->query.len, hm->query.ptr));
// MG_INFO(("Chunk data:\n%.*s", (int) hm->chunk.len, hm->chunk.ptr));
mg_http_delete_chunk(c, hm);
if (hm->chunk.len == 0) {
MG_INFO(("Last chunk received, sending response"));
mg_http_reply(c, 200, "", "ok (chunked)\n");
}
} else if (ev == MG_EV_HTTP_MSG && mg_http_match_uri(hm, "/upload")) {
MG_INFO(("Got all %lu bytes!", (unsigned long) hm->body.len));
MG_INFO(("Query string: [%.*s]", (int) hm->query.len, hm->query.ptr));
// MG_INFO(("Body:\n%.*s", (int) hm->body.len, hm->body.ptr));
mg_http_reply(c, 200, "", "ok (%lu)\n", (unsigned long) hm->body.len);
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_serve_opts opts = {.root_dir = "web_root"};
mg_http_serve_dir(c, hm, &opts);
}
}
int main(void) {
struct mg_mgr mgr;
mg_mgr_init(&mgr);
mg_log_set(MG_LL_DEBUG); // Set debug log level
mg_http_listen(&mgr, "http://localhost:8000", cb, NULL);
for (;;) mg_mgr_poll(&mgr, 50);
mg_mgr_free(&mgr);
return 0;
}

View File

@@ -0,0 +1,64 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>example</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
#container { margin-right: auto; margin-left: auto; max-width: 480px; }
#info { background: #e0f0f0; border-radius: .5em; padding: 2em; }
#wrapper { margin-top: 1em; }
</style>
</head>
<body>
<div id="container">
<div id="info">
Mongoose always buffers a full HTTP message before invoking
MG_EV_HTTP_MSG event. Big POST request require of lot
of RAM to buffer everything.
<br><br>
In order to upload large files to a memory-constrained system, use
<code>MG_EV_HTTP_CHUNK</code> on a server side. It fires when
a partial HTTP message has been received (or a chunk-encoded chunk).
Use <code>mg_http_delete_chunk()</code> to release chunk memory.
When 0-sized chunk is received, that's the end of the message.
Use <code>MG_MAX_RECV_SIZE</code> build constant to limit
maximum chunk size on a server side.
<br><br>
In this example, JavaScript code uses "fetch()" browser API.
Uploaded file is not saved, but rather printed by server side.
</div>
<div id="wrapper">
<input type="file" id="el1" style="display: none"/>
<button id="el2">choose file...</button>
<div id="el3" style="margin-top: 1em;"></div>
</div>
</div>
</body>
<script>
// When user clicks on a button, trigger file selection dialog
document.getElementById('el2').onclick = function(ev) {
document.getElementById('el1').click();
};
// If user selected a file, read it into memory and trigger sendFileData()
document.getElementById('el1').onchange = function(ev) {
if (!ev.target.files[0]) return;
var f = ev.target.files[0], r = new FileReader();
r.readAsArrayBuffer(f);
r.onload = function() {
ev.target.value = '';
document.getElementById('el3').innerText = 'Uploading...';
fetch('/upload?name=' + encodeURIComponent(f.name), {
method: 'POST',
body: r.result,
}).then(function(res) {
document.getElementById('el3').innerText = 'Uploaded ' + r.result.byteLength + ' bytes';
});
};
};
</script>
</html>

View File

@@ -0,0 +1 @@
See detailed tutorial at https://mongoose.ws/tutorials/http-client/

View File

@@ -0,0 +1 @@
../../test/data/ca.pem

View File

@@ -0,0 +1,72 @@
// Copyright (c) 2021 Cesanta Software Limited
// All rights reserved
//
// Example HTTP client. Connect to `s_url`, send request, wait for a response,
// print the response and exit.
// You can change `s_url` from the command line by executing: ./example YOUR_URL
//
// To enable SSL/TLS, make SSL=OPENSSL or make SSL=MBEDTLS
#include "mongoose.h"
// The very first web page in history. You can replace it from command line
static const char *s_url = "http://info.cern.ch/";
static const char *s_post_data = NULL; // POST data
static const uint64_t s_timeout_ms = 1500; // Connect timeout in milliseconds
// Print HTTP response and signal that we're done
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_OPEN) {
// Connection created. Store connect expiration time in c->label
*(uint64_t *) c->label = mg_millis() + s_timeout_ms;
} else if (ev == MG_EV_POLL) {
if (mg_millis() > *(uint64_t *) c->label &&
(c->is_connecting || c->is_resolving)) {
mg_error(c, "Connect timeout");
}
} else if (ev == MG_EV_CONNECT) {
// Connected to server. Extract host name from URL
struct mg_str host = mg_url_host(s_url);
// If s_url is https://, tell client connection to use TLS
if (mg_url_is_ssl(s_url)) {
struct mg_tls_opts opts = {.ca = "ca.pem", .srvname = host};
mg_tls_init(c, &opts);
}
// Send request
int content_length = s_post_data ? strlen(s_post_data) : 0;
mg_printf(c,
"%s %s HTTP/1.0\r\n"
"Host: %.*s\r\n"
"Content-Type: octet-stream\r\n"
"Content-Length: %d\r\n"
"\r\n",
s_post_data ? "POST" : "GET", mg_url_uri(s_url), (int) host.len,
host.ptr, content_length);
mg_send(c, s_post_data, content_length);
} else if (ev == MG_EV_HTTP_MSG) {
// Response is received. Print it
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
printf("%.*s", (int) hm->message.len, hm->message.ptr);
c->is_closing = 1; // Tell mongoose to close this connection
*(bool *) fn_data = true; // Tell event loop to stop
} else if (ev == MG_EV_ERROR) {
*(bool *) fn_data = true; // Error, tell event loop to stop
}
}
int main(int argc, char *argv[]) {
const char *log_level = getenv("LOG_LEVEL"); // Allow user to set log level
if (log_level == NULL) log_level = "4"; // Default is verbose
struct mg_mgr mgr; // Event manager
bool done = false; // Event handler flips it to true
if (argc > 1) s_url = argv[1]; // Use URL provided in the command line
mg_log_set(atoi(log_level)); // Set to 0 to disable debug
mg_mgr_init(&mgr); // Initialise event manager
mg_http_connect(&mgr, s_url, fn, &done); // Create client connection
while (!done) mg_mgr_poll(&mgr, 50); // Event manager loops until 'done'
mg_mgr_free(&mgr); // Free resources
return 0;
}

View File

@@ -0,0 +1 @@
../../test/data/ca.pem

View File

@@ -0,0 +1,65 @@
// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
//
// Example HTTP client that uses a proxy server. Usage:
// make
// ./example PROXY:PORT http://www.ladyada.net
//
// To enable SSL/TLS, make SSL=OPENSSL or make SSL=MBEDTLS
//
#include "mongoose.h"
// Print HTTP response and signal that we're done
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
const char *url = fn_data;
static bool connected;
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
printf("%.*s", (int) hm->message.len, hm->message.ptr);
exit(EXIT_SUCCESS);
} else if (ev == MG_EV_CONNECT) {
// Proxy TCP connection established. Send CONNECT request
struct mg_str host = mg_url_host(url);
// c->is_hexdumping = 1;
mg_printf(c, "CONNECT %.*s:%hu HTTP/1.1\r\nHost: %.*s:%hu\r\n\r\n",
(int) host.len, host.ptr, mg_url_port(url), (int) host.len,
host.ptr, mg_url_port(url));
// If target URL is SSL/TLS, command client connection to use TLS
if (mg_url_is_ssl(url)) {
struct mg_tls_opts opts = {.ca = "ca.pem"};
mg_tls_init(c, &opts);
}
} else if (!connected && ev == MG_EV_READ) {
struct mg_http_message hm;
int n = mg_http_parse((char *) c->recv.buf, c->recv.len, &hm);
if (n > 0) {
struct mg_str host = mg_url_host(url);
// CONNECT response - tunnel is established
connected = true;
MG_DEBUG(
("Connected to proxy, status: %.*s", (int) hm.uri.len, hm.uri.ptr));
mg_iobuf_del(&c->recv, 0, n);
// Send request to the target server
mg_printf(c, "GET %s HTTP/1.0\r\n"
"Host: %.*s\r\n"
"\r\n",
mg_url_uri(url), (int) host.len, host.ptr);
}
}
}
int main(int argc, char *argv[]) {
struct mg_mgr mgr;
if (argc != 3) {
fprintf(stderr, "Usage: %s PROXY_URL URL\n", argv[0]);
return EXIT_FAILURE;
}
mg_mgr_init(&mgr); // Initialise event manager
mg_http_connect(&mgr, argv[1], fn, argv[2]); // Connect to the proxy
for (;;) mg_mgr_poll(&mgr, 1000); // Event loop
mg_mgr_free(&mgr);
return 0;
}

View File

@@ -0,0 +1 @@
../../test/data/ca.pem

View File

@@ -0,0 +1,68 @@
// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
//
// HTTP server example. This server serves both static and dynamic content.
// It opens two ports: plain HTTP on port 8000 and HTTP on port 8443.
// It implements the following endpoints:
// /api/stats - respond with free-formatted stats on current connections
// /api/f2/:id - wildcard example, respond with JSON string {"result": "URI"}
// any other URI serves static files from s_root_dir
//
// To enable SSL/TLS (using self-signed certificates in PEM files),
// 1. make SSL=OPENSSL or make SSL=MBEDTLS
// 2. curl -k https://127.0.0.1:8443
#include "mongoose.h"
static const char *s_http_addr = "http://0.0.0.0:8000"; // HTTP port
static const char *s_https_addr = "https://0.0.0.0:8443"; // HTTPS port
static const char *s_root_dir = ".";
// We use the same event handler function for HTTP and HTTPS connections
// fn_data is NULL for plain HTTP, and non-NULL for HTTPS
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_ACCEPT && fn_data != NULL) {
struct mg_tls_opts opts = {
//.ca = "ca.pem", // Uncomment to enable two-way SSL
.cert = "server.pem", // Certificate PEM file
.certkey = "server.pem", // This pem contains both cert and key
};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_http_match_uri(hm, "/api/stats")) {
// Print some statistics about currently established connections
mg_printf(c, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
mg_http_printf_chunk(c, "ID PROTO TYPE LOCAL REMOTE\n");
for (struct mg_connection *t = c->mgr->conns; t != NULL; t = t->next) {
char loc[40], rem[40];
mg_http_printf_chunk(c, "%-3lu %4s %s %-15s %s\n", t->id,
t->is_udp ? "UDP" : "TCP",
t->is_listening ? "LISTENING"
: t->is_accepted ? "ACCEPTED "
: "CONNECTED",
mg_straddr(&t->loc, loc, sizeof(loc)),
mg_straddr(&t->rem, rem, sizeof(rem)));
}
mg_http_printf_chunk(c, ""); // Don't forget the last empty chunk
} else if (mg_http_match_uri(hm, "/api/f2/*")) {
mg_http_reply(c, 200, "", "{\"result\": \"%.*s\"}\n", (int) hm->uri.len,
hm->uri.ptr);
} else {
struct mg_http_serve_opts opts = {.root_dir = s_root_dir};
mg_http_serve_dir(c, ev_data, &opts);
}
}
(void) fn_data;
}
int main(void) {
struct mg_mgr mgr; // Event manager
mg_log_set(MG_LL_DEBUG); // Set log level
mg_mgr_init(&mgr); // Initialise event manager
mg_http_listen(&mgr, s_http_addr, fn, NULL); // Create HTTP listener
mg_http_listen(&mgr, s_https_addr, fn, (void *) 1); // HTTPS listener
for (;;) mg_mgr_poll(&mgr, 1000); // Infinite event loop
mg_mgr_free(&mgr);
return 0;
}

View File

@@ -0,0 +1,50 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
6e:73:28:55:df:13:b5:61:f5:4f:4f:5d:00:d9:0a:d8:b5:3a:21:4b
Signature Algorithm: ecdsa-with-SHA256
Issuer: C = IE, L = Dublin, O = Cesanta, CN = Test Root
Validity
Not Before: May 9 21:51:49 2020 GMT
Not After : May 9 21:51:49 2030 GMT
Subject: CN = server
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:92:e0:46:9c:89:c3:37:a9:74:eb:35:55:43:55:
5c:ac:eb:c7:e4:50:ee:f4:c0:ba:17:02:5c:d9:ed:
b4:d4:ff:21:12:9a:b4:43:f4:89:4b:69:e4:6d:2b:
96:1f:fc:01:4d:30:5a:79:73:76:ba:19:41:cc:c5:
16:2b:bf:74:28
ASN1 OID: prime256v1
NIST CURVE: P-256
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
X509v3 Key Usage:
Digital Signature, Key Encipherment, Key Agreement
X509v3 Extended Key Usage:
TLS Web Server Authentication
Signature Algorithm: ecdsa-with-SHA256
30:46:02:21:00:fa:3a:c7:1e:cb:8c:27:59:41:8d:77:dd:7b:
cb:8c:08:15:16:b9:6e:70:e6:47:38:d1:55:42:e0:d7:66:c8:
f0:02:21:00:cc:70:4d:96:28:00:d3:c7:39:53:74:b2:49:87:
27:92:1b:ab:1a:0e:74:06:59:42:23:47:98:43:d8:20:a7:fa
-----BEGIN CERTIFICATE-----
MIIBhzCCASygAwIBAgIUbnMoVd8TtWH1T09dANkK2LU6IUswCgYIKoZIzj0EAwIw
RDELMAkGA1UEBhMCSUUxDzANBgNVBAcMBkR1YmxpbjEQMA4GA1UECgwHQ2VzYW50
YTESMBAGA1UEAwwJVGVzdCBSb290MB4XDTIwMDUwOTIxNTE0OVoXDTMwMDUwOTIx
NTE0OVowETEPMA0GA1UEAwwGc2VydmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD
QgAEkuBGnInDN6l06zVVQ1VcrOvH5FDu9MC6FwJc2e201P8hEpq0Q/SJS2nkbSuW
H/wBTTBaeXN2uhlBzMUWK790KKMvMC0wCQYDVR0TBAIwADALBgNVHQ8EBAMCA6gw
EwYDVR0lBAwwCgYIKwYBBQUHAwEwCgYIKoZIzj0EAwIDSQAwRgIhAPo6xx7LjCdZ
QY133XvLjAgVFrlucOZHONFVQuDXZsjwAiEAzHBNligA08c5U3SySYcnkhurGg50
BllCI0eYQ9ggp/o=
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQglNni0t9Dg9icgG8w
kbfxWSS+TuNgbtNybIQXcm3NHpmhRANCAASS4EacicM3qXTrNVVDVVys68fkUO70
wLoXAlzZ7bTU/yESmrRD9IlLaeRtK5Yf/AFNMFp5c3a6GUHMxRYrv3Qo
-----END PRIVATE KEY-----

View File

@@ -0,0 +1 @@
../../test/data/ca.pem

View File

@@ -0,0 +1,85 @@
// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
//
// Example HTTP reverse proxy
// 1. Run `make`. This builds and starts a proxy on port 8000
// 2. Start your browser, go to https://localhost:8000
//
// To enable SSL/TLS, add SSL=OPENSSL or SSL=MBEDTLS
static const char *s_backend_url =
#if defined(MG_ENABLE_MBEDTLS) || defined(MG_ENABLE_OPENSSL)
"https://cesanta.com";
#else
"http://info.cern.ch";
#endif
static const char *s_listen_url = "http://localhost:8000";
#include "mongoose.h"
// Forward client request to the backend connection, rewriting the Host header
static void forward_request(struct mg_http_message *hm,
struct mg_connection *c) {
size_t i, max = sizeof(hm->headers) / sizeof(hm->headers[0]);
struct mg_str host = mg_url_host(s_backend_url);
mg_printf(c, "%.*s\r\n",
(int) (hm->proto.ptr + hm->proto.len - hm->message.ptr),
hm->message.ptr);
for (i = 0; i < max && hm->headers[i].name.len > 0; i++) {
struct mg_str *k = &hm->headers[i].name, *v = &hm->headers[i].value;
if (mg_strcmp(*k, mg_str("Host")) == 0) v = &host;
mg_printf(c, "%.*s: %.*s\r\n", (int) k->len, k->ptr, (int) v->len, v->ptr);
}
mg_send(c, "\r\n", 2);
mg_send(c, hm->body.ptr, hm->body.len);
MG_DEBUG(("FORWARDING: %.*s %.*s", (int) hm->method.len, hm->method.ptr,
(int) hm->uri.len, hm->uri.ptr));
}
static void fn2(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
struct mg_connection *c2 = fn_data;
if (ev == MG_EV_READ) {
// All incoming data from the backend, forward to the client
if (c2 != NULL) mg_send(c2, c->recv.buf, c->recv.len);
mg_iobuf_del(&c->recv, 0, c->recv.len);
} else if (ev == MG_EV_CLOSE) {
if (c2 != NULL) c2->fn_data = NULL;
}
(void) ev_data;
}
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
struct mg_connection *c2 = fn_data;
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
// Client request, create backend connection Note that we're passing
// client connection `c` as fn_data for the created backend connection.
c2 = mg_connect(c->mgr, s_backend_url, fn2, c);
if (c2 == NULL) {
mg_error(c, "Cannot create backend connection");
} else {
if (mg_url_is_ssl(s_backend_url)) {
struct mg_tls_opts opts = {.ca = "ca.pem"};
mg_tls_init(c2, &opts);
}
c->fn_data = c2;
forward_request(hm, c2);
c2->is_hexdumping = 1;
}
} else if (ev == MG_EV_CLOSE) {
if (c2 != NULL) c2->is_closing = 1;
if (c2 != NULL) c2->fn_data = NULL;
}
}
int main(void) {
struct mg_mgr mgr;
mg_log_set(MG_LL_DEBUG); // Set log level
mg_mgr_init(&mgr); // Initialise event manager
mg_http_listen(&mgr, s_listen_url, fn, NULL); // Start proxy
for (;;) mg_mgr_poll(&mgr, 1000); // Event loop
mg_mgr_free(&mgr);
return 0;
}

View File

@@ -0,0 +1 @@
See detailed tutorial at https://mongoose.ws/tutorials/http-server/

View File

@@ -0,0 +1,103 @@
// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
#include <signal.h>
#include "mongoose.h"
static int s_debug_level = MG_LL_INFO;
static const char *s_root_dir = ".";
static const char *s_listening_address = "http://0.0.0.0:8000";
static const char *s_enable_hexdump = "no";
static const char *s_ssi_pattern = "#.html";
// Handle interrupts, like Ctrl-C
static int s_signo;
static void signal_handler(int signo) {
s_signo = signo;
}
// Event handler for the listening connection.
// Simply serve static files from `s_root_dir`
static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = ev_data, tmp = {0};
struct mg_str unknown = mg_str_n("?", 1), *cl;
struct mg_http_serve_opts opts = {0};
opts.root_dir = s_root_dir;
opts.ssi_pattern = s_ssi_pattern;
mg_http_serve_dir(c, hm, &opts);
mg_http_parse((char *) c->send.buf, c->send.len, &tmp);
cl = mg_http_get_header(&tmp, "Content-Length");
if (cl == NULL) cl = &unknown;
MG_INFO(("%.*s %.*s %.*s %.*s", (int) hm->method.len, hm->method.ptr,
(int) hm->uri.len, hm->uri.ptr, (int) tmp.uri.len, tmp.uri.ptr,
(int) cl->len, cl->ptr));
}
(void) fn_data;
}
static void usage(const char *prog) {
fprintf(stderr,
"Mongoose v.%s\n"
"Usage: %s OPTIONS\n"
" -H yes|no - enable traffic hexdump, default: '%s'\n"
" -S PAT - SSI filename pattern, default: '%s'\n"
" -d DIR - directory to serve, default: '%s'\n"
" -l ADDR - listening address, default: '%s'\n"
" -v LEVEL - debug level, from 0 to 4, default: %d\n",
MG_VERSION, prog, s_enable_hexdump, s_ssi_pattern, s_root_dir,
s_listening_address, s_debug_level);
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[]) {
char path[MG_PATH_MAX] = ".";
struct mg_mgr mgr;
struct mg_connection *c;
int i;
// Parse command-line flags
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "-d") == 0) {
s_root_dir = argv[++i];
} else if (strcmp(argv[i], "-H") == 0) {
s_enable_hexdump = argv[++i];
} else if (strcmp(argv[i], "-S") == 0) {
s_ssi_pattern = argv[++i];
} else if (strcmp(argv[i], "-l") == 0) {
s_listening_address = argv[++i];
} else if (strcmp(argv[i], "-v") == 0) {
s_debug_level = atoi(argv[++i]);
} else {
usage(argv[0]);
}
}
// Root directory must not contain double dots. Make it absolute
// Do the conversion only if the root dir spec does not contain overrides
if (strchr(s_root_dir, ',') == NULL) {
realpath(s_root_dir, path);
s_root_dir = path;
}
// Initialise stuff
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
mg_log_set(s_debug_level);
mg_mgr_init(&mgr);
if ((c = mg_http_listen(&mgr, s_listening_address, cb, &mgr)) == NULL) {
MG_ERROR(("Cannot listen on %s. Use http://ADDR:PORT or :PORT",
s_listening_address));
exit(EXIT_FAILURE);
}
if (mg_casecmp(s_enable_hexdump, "yes") == 0) c->is_hexdumping = 1;
// Start infinite event loop
MG_INFO(("Mongoose version : v%s", MG_VERSION));
MG_INFO(("Listening on : %s", s_listening_address));
MG_INFO(("Web root : [%s]", s_root_dir));
while (s_signo == 0) mg_mgr_poll(&mgr, 1000);
mg_mgr_free(&mgr);
MG_INFO(("Exiting on signal %d", s_signo));
return 0;
}

View File

@@ -0,0 +1 @@
../../test/data/ca.pem

View File

@@ -0,0 +1,70 @@
// Copyright (c) 2021 Cesanta Software Limited
// All rights reserved
//
// Example HTTP client.
// Instead of buffering the whole HTTP response in memory and wait for the
// MG_EV_HTTP_MSG event, this client catches MG_EV_HTTP_CHUNK events and
// prints them. This is useful to download large content without spooling
// everything to memory, or catch chunked content.
//
// You can change `s_url` from the command line by executing: ./example YOUR_URL
//
// To enable SSL/TLS for this client, build it like this:
// make MBEDTLS=/path/to/your/mbedtls/installation
// make OPENSSL=/path/to/your/openssl/installation
#include "mongoose.h"
// The very first web page in history. You can replace it from command line
static const char *s_url = "http://info.cern.ch";
// Print HTTP response and signal that we're done
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_CONNECT) {
// Connected to server. Extract host name from URL
struct mg_str host = mg_url_host(s_url);
// If s_url is https://, tell client connection to use TLS
if (mg_url_is_ssl(s_url)) {
struct mg_tls_opts opts = {.ca = "ca.pem", .srvname = host};
mg_tls_init(c, &opts);
}
// Send request
mg_printf(c,
"GET %s HTTP/1.1\r\n"
"Connection: keep-alive\r\n"
"Keep-Alive: timeout=60\r\n"
"Host: %.*s\r\n"
"\r\n",
mg_url_uri(s_url), (int) host.len, host.ptr);
} else if (ev == MG_EV_HTTP_CHUNK) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
fwrite(hm->chunk.ptr, 1, hm->chunk.len, stdout);
// fprintf(stderr, "c %u\n", (unsigned) hm->chunk.len);
mg_http_delete_chunk(c, hm);
if (hm->chunk.len == 0) *(bool *) fn_data = true; // Last chunk
} else if (ev == MG_EV_HTTP_MSG) {
// Response is received. Print it
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
fwrite(hm->body.ptr, 1, hm->body.len, stdout);
c->is_closing = 1; // Tell mongoose to close this connection
*(bool *) fn_data = true; // Tell event loop to stop
} else if (ev == MG_EV_ERROR) {
*(bool *) fn_data = true; // Error, tell event loop to stop
}
}
int main(int argc, char *argv[]) {
struct mg_mgr mgr; // Event manager
bool done = false; // Event handler flips it to true
const char *log_level = getenv("V"); // Log level
if (log_level == NULL) log_level = "3"; // If not set, set to DEBUG
mg_log_set(atoi(log_level)); // Set to 0 to disable debug log
if (argc > 1) s_url = argv[1]; // Use URL from command line
mg_mgr_init(&mgr); // Initialise event manager
mg_http_connect(&mgr, s_url, fn, &done); // Create client connection
while (!done) mg_mgr_poll(&mgr, 1000); // Infinite event loop
mg_mgr_free(&mgr); // Free resources
return 0;
}

View File

@@ -0,0 +1,89 @@
// Copyright (c) 2021 Cesanta Software Limited
// All rights reserved
//
// Example that demonstrates how to send a large responses with limited memory.
// We're going to send a JSON array of many integer values, s_data.
// The idea is to send a response in small chunks, and let the client request
// the next chunk.
// Periodically, s_data changes, which is tracked by s_version.
// Client requests a range and a version, to ensure data integrity.
//
// 1. Start this server, type `make`
// 2. Open http://localhost:8000 in your browser
#include "mongoose.h"
static const char *s_listen_on = "http://localhost:8000";
static const char *s_root_dir = "web_root";
#define DATA_SIZE 10000 // Total number of elements
#define CHUNK_SIZE 100 // Max number returned in one API call
static int s_data[DATA_SIZE]; // Simulate some complex big data
static long s_version = 0; // Data "version"
static long getparam(struct mg_http_message *hm, const char *json_path) {
double dv = 0;
mg_json_get_num(hm->body, json_path, &dv);
return dv;
}
static size_t printdata(mg_pfn_t out, void *ptr, va_list *ap) {
unsigned start = va_arg(*ap, unsigned);
unsigned max = start + CHUNK_SIZE;
const char *comma = "";
size_t n = 0;
if (max > DATA_SIZE) max = DATA_SIZE;
while (start < max) {
n += mg_xprintf(out, ptr, "%s%d", comma, s_data[start]);
comma = ",";
start++;
}
return n;
}
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = ev_data;
if (mg_http_match_uri(hm, "/api/data")) {
const char *headers = "content-type: text/json\r\n";
long start = getparam(hm, "$.start");
long version = getparam(hm, "$.version");
MG_DEBUG(("%.*s", (int) hm->body.len, hm->body.ptr));
if (version > 0 && version != s_version) {
// Version mismatch: s_data has changed while client fetches it
// Tell client to restart
mg_http_reply(c, 200, headers, "{%Q:%Q, %Q:%ld}", "error",
"wrong version", "version", version);
} else {
// Return data, up to CHUNK_SIZE elements
mg_http_reply(c, 200, headers, "{%Q:%ld,%Q:%ld,%Q:[%M]}", "version",
s_version, "start", start, "data", printdata, start);
}
} else {
struct mg_http_serve_opts opts = {0};
opts.root_dir = s_root_dir;
mg_http_serve_dir(c, hm, &opts);
}
}
(void) fn_data;
}
static void timer_fn(void *arg) {
for (int i = 0; i < DATA_SIZE; i++) {
s_data[i] = rand();
}
s_version++;
(void) arg;
}
int main(void) {
struct mg_mgr mgr;
mg_mgr_init(&mgr);
srand(time(NULL));
mg_timer_add(&mgr, 5000, MG_TIMER_REPEAT | MG_TIMER_RUN_NOW, timer_fn, NULL);
mg_http_listen(&mgr, s_listen_on, fn, NULL);
MG_INFO(("Listening on %s", s_listen_on));
for (;;) mg_mgr_poll(&mgr, 1000);
mg_mgr_free(&mgr);
return 0;
}

View File

@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>example</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
#container { margin-right: auto; margin-left: auto; max-width: 480px; }
#info { background: #e0f0f0; border-radius: .5em; padding: 2em; margin-bottom: 1em; }
#result { margin-top: 1em; }
</style>
</head>
<body>
<div id="container">
<div id="info">
On devices with limited RAM, it is important to limit the response size
of API calls. This example demonstrates how to fetch a large
data in smaller chunks, and guarantee its integrity.
Data gets returned in a series of request/response transactions,
where each response is small enough to fit into available device RAM.
<br/><br/>
Data integrity is implemented by versioning.
The idea is that the first response includes the current "version" of the
data, and that version is passed to all subsequent requests.
If data version changes in the middle of the request series,
client fails with 'wrong version' error.
</div>
<button id="btn">fetch data</button>
<div id="result"></div>
</div>
</body>
<script>
const getchunk = (start, version) =>
fetch('/api/data', {method: 'POST', body:JSON.stringify({start, version})})
.then(r => r.json());
document.getElementById('btn').onclick = function() {
var data = [], version = 0;
const load = offset => getchunk(offset, version)
.then(r => {
// console.log(r);
if (r.error) {
document.getElementById('result').innerText = 'Error: ' + r.error;
} else {
version = r.version;
data = data.concat(r.data);
if (r.data.length == 0) {
document.getElementById('result').innerText = 'Version: ' +
version + ', data: \n' +
JSON.stringify(data, null, 2);
} else {
load(offset + r.data.length, version);
}
}
});
load(0);
};
</script>
</html>

View File

@@ -0,0 +1 @@
See detailed tutorial at https://mongoose.ws/tutorials/json-rpc-over-websocket/

View File

@@ -0,0 +1,84 @@
// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
//
// See https://mongoose.ws/tutorials/json-rpc-over-websocket/
#include "mongoose.h"
static const char *s_listen_on = "ws://localhost:8000";
static const char *s_web_root = "web_root";
static struct mg_rpc *s_rpc_head = NULL;
static void rpc_sum(struct mg_rpc_req *r) {
double a = 0.0, b = 0.0;
mg_json_get_num(r->frame, "$.params[0]", &a);
mg_json_get_num(r->frame, "$.params[1]", &b);
mg_rpc_ok(r, "%g", a + b);
}
static void rpc_mul(struct mg_rpc_req *r) {
double a = 0.0, b = 0.0;
mg_json_get_num(r->frame, "$.params[0]", &a);
mg_json_get_num(r->frame, "$.params[1]", &b);
mg_rpc_ok(r, "%g", a * b);
}
// This RESTful server implements the following endpoints:
// /websocket - upgrade to Websocket, and implement websocket echo server
// any other URI serves static files from s_web_root
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_OPEN) {
// c->is_hexdumping = 1;
} else if (ev == MG_EV_WS_OPEN) {
c->label[0] = 'W'; // Mark this connection as an established WS client
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_http_match_uri(hm, "/websocket")) {
// Upgrade to websocket. From now on, a connection is a full-duplex
// Websocket connection, which will receive MG_EV_WS_MSG events.
mg_ws_upgrade(c, hm, NULL);
} else {
// Serve static files
struct mg_http_serve_opts opts = {.root_dir = s_web_root};
mg_http_serve_dir(c, ev_data, &opts);
}
} else if (ev == MG_EV_WS_MSG) {
// Got websocket frame. Received data is wm->data
struct mg_ws_message *wm = (struct mg_ws_message *) ev_data;
struct mg_iobuf io = {0, 0, 0, 512};
struct mg_rpc_req r = {&s_rpc_head, 0, mg_pfn_iobuf, &io, 0, wm->data};
mg_rpc_process(&r);
if (io.buf) mg_ws_send(c, (char *) io.buf, io.len, WEBSOCKET_OP_TEXT);
mg_iobuf_free(&io);
}
(void) fn_data;
}
static void timer_fn(void *arg) {
struct mg_mgr *mgr = (struct mg_mgr *) arg;
// Broadcast message to all connected websocket clients.
for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) {
if (c->label[0] != 'W') continue;
mg_ws_printf(c, WEBSOCKET_OP_TEXT, "{%Q:%Q,%Q:[%d,%d,%d]}", "method",
"notification1", "params", 1, 2, 3);
}
}
int main(void) {
struct mg_mgr mgr; // Event manager
mg_mgr_init(&mgr); // Init event manager
mg_log_set(MG_LL_DEBUG); // Set log level
mg_timer_add(&mgr, 5000, MG_TIMER_REPEAT, timer_fn, &mgr); // Init timer
// Configure JSON-RPC functions we're going to handle
mg_rpc_add(&s_rpc_head, mg_str("sum"), rpc_sum, NULL);
mg_rpc_add(&s_rpc_head, mg_str("mul"), rpc_mul, NULL);
mg_rpc_add(&s_rpc_head, mg_str("rpc.list"), mg_rpc_list, &s_rpc_head);
printf("Starting WS listener on %s/websocket\n", s_listen_on);
mg_http_listen(&mgr, s_listen_on, fn, NULL); // Create HTTP listener
for (;;) mg_mgr_poll(&mgr, 1000); // Infinite event loop
mg_mgr_free(&mgr); // Deallocate event manager
mg_rpc_del(&s_rpc_head, NULL); // Deallocate RPC handlers
return 0;
}

View File

@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<body>
<h1>JSON-RPC over Websocket demo</h1>
<input id="url" type="text" placeholder="Type URL" value="ws://localhost:8000/websocket" style="width:20em;" />
<button id="connect">connect</button>
<div style="height: 0.3em;">&nbsp;</div>
<button id="btn1">Calculate 1 + 2</button>
<button id="btn2">Calculate 2 * 3</button>
<div style="margin-top: 1em;">Event log:</div>
<div id="log" style="background: #eee; height: 10em; padding: 0.5em; overflow:auto;"><div>
</body>
<script src="rpc-over-websocket.js"></script>
<script>
var rpc, E = function(id) { return document.getElementById(id); };
var url = E('url'), connect = E('connect'), btn1 = E('btn1'), btn2 = E('btn2'), msglog = E('log');
var enable = function(en) { btn1.disabled = btn2.disabled = !en; url.disabled = en; connect.innerHTML = en ? 'disconnect' : 'connect'; };
var log = text => msglog.innerHTML += text + '<br/>\n';
enable(false);
connect.onclick = function() {
console.log(rpc);
if (rpc) { rpc.close(); rpc = null; return; }
rpc = jsonrpc(url.value,
() => enable(true),
() => enable(false),
msg => log('NOTIFICATION: ' + JSON.stringify(msg)));
};
btn1.onclick = ev => rpc.call('sum', [1, 2]).then(res => log('SUM:' + JSON.stringify(res)));
btn2.onclick = ev => rpc.call('mul', [2, 3]).then(res => log('MUL:' + JSON.stringify(res)));
</script>
</html>

View File

@@ -0,0 +1,36 @@
// JSON-RPC over Websocket implementation
var JSONRPC_TIMEOUT_MS = 1000;
var jsonrpc = function(url, onopen, onclose, onnotification) {
var rpcid = 0, pending = {}, ws = new WebSocket(url);
if (!ws) return null;
ws.onclose = onclose;
ws.onmessage = function(ev) {
const frame = JSON.parse(ev.data);
console.log('rcvd', frame, 'pending:', pending);
if (frame.id !== undefined) {
if (pending[frame.id] !== undefined) pending[frame.id](frame); // Resolve
delete (pending[frame.id]);
} else {
if (onnotification) onnotification(frame);
}
};
if (onopen) onopen();
return {
close: () => ws.close(),
call: function(method, params) {
const id = rpcid++, request = {id, method, params};
ws.send(JSON.stringify(request));
console.log('sent', request);
return new Promise(function(resolve, reject) {
setTimeout(JSONRPC_TIMEOUT_MS, function() {
if (pending[id] === undefined) return;
log('Timing out frame ', JSON.stringify(request));
delete (pending[id]);
reject();
});
pending[id] = x => resolve(x);
});
},
};
};

View File

@@ -0,0 +1,61 @@
// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
#include "mongoose.h"
// HTTP request handler function. It implements the following endpoints:
// /api/log/static - returns contents of log.txt file
// /api/log/live - hangs forever, and returns live log messages
// all other URI - serves web_root/ directory
static void cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_http_match_uri(hm, "/api/log/static")) {
struct mg_http_serve_opts opts = {.root_dir = NULL};
mg_http_serve_file(c, hm, "log.txt", &opts);
} else if (mg_http_match_uri(hm, "/api/log/live")) {
c->label[0] = 'L'; // Mark that connection as live log listener
mg_printf(c, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
} else {
struct mg_http_serve_opts opts = {.root_dir = "web_root"};
mg_http_serve_dir(c, ev_data, &opts);
}
}
}
static void log_message(const char *filename, const char *message) {
FILE *fp = fopen(filename, "a");
if (fp != NULL) {
fprintf(fp, "%s", message);
fclose(fp);
}
}
static void broadcast_message(struct mg_mgr *mgr, const char *message) {
struct mg_connection *c;
for (c = mgr->conns; c != NULL; c = c->next) {
if (c->label[0] == 'L') mg_http_printf_chunk(c, "%s", message);
}
}
// Timer function - called periodically.
// Prepare log message. Save it to a file, and broadcast.
static void timer_fn(void *arg) {
char buf[64];
snprintf(buf, sizeof(buf), "Time is: %lu\n", (unsigned long) time(NULL));
log_message("log.txt", buf);
broadcast_message(arg, buf);
}
int main(void) {
struct mg_mgr mgr;
mg_mgr_init(&mgr);
mg_http_listen(&mgr, "http://localhost:8000", cb, NULL);
mg_timer_add(&mgr, 1000, MG_TIMER_REPEAT, timer_fn, &mgr);
for (;;) mg_mgr_poll(&mgr, 50);
mg_mgr_free(&mgr);
return 0;
}

View File

@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>example</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
#container { margin-right: auto; margin-left: auto; max-width: 480px; }
#info { background: #e0f0f0; border-radius: .5em; padding: 2em; }
#static, #live { background: #f0f0f0; border-radius: .5em; padding: 0.5em; max-height: 10em; overflow: auto; height: 100%; }
#wrapper { display: flex; justify-content: flex-evenly; margin-top: 1em; }
</style>
</head>
<body>
<div id="container">
<div id="info">
A div below shows file log.txt, which is produced by the server.
The server appends a new log message to that file every second,
and a div below also shows that, implementing a "live log" feature.
JS code on this page first fetches /api/log/static that returns
log.txt contents, then fetches /api/log/live that does not return,
but feeds chunks of data as they arrive (live log).
<br/><br/>
You can also use <code>curl</code> command-line utility to see
live logs:
<br/><code>curl localhost:8000/api/log/live</code>
</div>
<div id="wrapper">
<div style="width: 50%"><b>Static</b><pre id="static"></pre></div>
<div style="width: 1em;"></div>
<div style="width: 50%"><b>Live</b><pre id="live"></pre></div>
</div>
</div>
</body>
<script>
var f = function(r) {
return r.read().then(function(result) {
var data = String.fromCharCode.apply(null, result.value);
document.getElementById('live').innerText += data; // Append live log
if (!result.done) f(r); // Read next chunk
});
};
window.onload = ev => fetch('/api/log/static')
.then(r => r.text())
.then(r => { document.getElementById('static').innerText = r; })
.then(r => fetch('/api/log/live'))
.then(r => r.body.getReader())
.then(f);
</script>
</html>

View File

@@ -0,0 +1,132 @@
// Copyright (c) 2022 Cesanta Software Limited
// All rights reserved
//
// SNTP example using MIP and pcap driver
#include <pcap.h>
#include "mongoose.h"
static struct mg_connection *s_sntp_conn = NULL;
static int s_signo;
void signal_handler(int signo) {
s_signo = signo;
}
// SNTP client callback
static void sfn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_SNTP_TIME) {
int64_t t = *(int64_t *) ev_data;
MG_INFO(("Got SNTP time: %lld ms from epoch", t));
} else if (ev == MG_EV_CLOSE) {
s_sntp_conn = NULL;
}
(void) fn_data, (void) c;
}
// Called every 5 seconds. Increase that for production case.
static void timer_fn(void *arg) {
struct mg_mgr *mgr = (struct mg_mgr *) arg;
if (s_sntp_conn == NULL) s_sntp_conn = mg_sntp_connect(mgr, NULL, sfn, NULL);
if (s_sntp_conn != NULL) mg_sntp_request(s_sntp_conn);
}
static int fail(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
return EXIT_FAILURE;
}
static size_t pcap_tx(const void *data, size_t len, void *userdata) {
int res = pcap_inject((pcap_t *) userdata, data, len);
if (res == PCAP_ERROR) {
MG_ERROR(("pcap_inject: %d", res));
}
return len;
}
static bool pcap_status(void *userdata) {
return userdata ? true : false;
}
static size_t pcap_rx(void *buf, size_t len, void *userdata) {
size_t received = 0;
struct pcap_pkthdr *hdr = NULL;
const unsigned char *pkt = NULL;
if (pcap_next_ex((pcap_t *) userdata, &hdr, &pkt) == 1) {
received = hdr->len < len ? hdr->len : len;
memcpy(buf, pkt, received);
}
return received;
}
int main(int argc, char *argv[]) {
const char *iface = "lo0"; // Network iface
const char *mac = "aa:bb:cc:01:02:03"; // MAC address
const char *bpf = NULL; // "host x.x.x.x or ether host ff:ff:ff:ff:ff:ff";
char errbuf[PCAP_ERRBUF_SIZE] = "";
struct mg_mgr mgr; // Event manager
mg_mgr_init(&mgr); // Initialise event manager
mg_log_set(MG_LL_DEBUG); // Set debug log level
mg_timer_add(&mgr, 3000, MG_TIMER_REPEAT | MG_TIMER_RUN_NOW, timer_fn, &mgr);
// Parse options
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-i") == 0 && i + 1 < argc) {
iface = argv[++i];
} else if (strcmp(argv[i], "-mac") == 0 && i + 1 < argc) {
mac = argv[++i];
} else if (strcmp(argv[i], "-bpf") == 0 && i + 1 < argc) {
bpf = argv[++i];
} else {
return fail("unknown option %s", argv[i]);
}
}
// Open network interface
pcap_t *ph = pcap_open_live(iface, 0xffff, 1, 1, errbuf);
if (ph == NULL) {
pcap_if_t *devs, *d;
MG_ERROR(("Failed to open interface %s. Available interfaces:", iface));
if (pcap_findalldevs(&devs, errbuf) == 0) {
for (d = devs; d != NULL; d = d->next) {
MG_ERROR(("%s (%s)", d->name, d->description ? d->description : ""));
}
pcap_freealldevs(devs);
}
fail("pcap_open_live: %s\n", errbuf);
}
pcap_setnonblock(ph, 1, errbuf);
// Apply BPF to reduce noise. Let in only broadcasts and our own traffic
if (bpf != NULL) {
struct bpf_program bpfp;
if (pcap_compile(ph, &bpfp, bpf, 1, 0)) fail("compile \n");
pcap_setfilter(ph, &bpfp);
pcap_freecode(&bpfp);
}
MG_INFO(("Opened interface %s\n", iface));
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
struct mip_ipcfg ipcfg = {.ip = 0, .mask = 0, .gw = 0};
sscanf(mac, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &ipcfg.mac[0], &ipcfg.mac[1],
&ipcfg.mac[2], &ipcfg.mac[3], &ipcfg.mac[4], &ipcfg.mac[5]);
struct mip_driver driver = {
.tx = pcap_tx, .status = pcap_status, .rx = pcap_rx};
mip_init(&mgr, &ipcfg, &driver, ph);
MG_INFO(("Init done, starting main loop"));
while (s_signo == 0) mg_mgr_poll(&mgr, 1); // Infinite event loop
mg_mgr_free(&mgr);
pcap_close(ph);
printf("Exiting on signal %d\n", s_signo);
return 0;
}

View File

@@ -0,0 +1 @@
See detailed tutorial at https://mongoose.ws/tutorials/aws-iot/

View File

@@ -0,0 +1 @@
../../test/data/ca.pem

View File

@@ -0,0 +1,91 @@
// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
//
// Example MQTT client. It performs the following steps:
// 1. Connects to the AWS IoT MQTT server
// 2. When connected, subscribes to the topic `s_rx_topic`
// 3. Publishes message `hello` to the `s_tx_topic` periodically
//
// This example requires TLS support. By default, it is built with mbedTLS,
// therefore make sure mbedTLS is installed. To build with OpenSSL, execute:
// make clean all CFLAGS="-W -Wall -DMG_ENABLE_OPENSSL=1 -lssl"
// In order to get MQTT URL, login to AWS IoT, click on "Settings" on the left
// bar, copy the "Endpoint" URL.
static const char *s_url =
"mqtts://a1pjwh2bop1ojt-ats.iot.eu-west-1.amazonaws.com";
// To create certificates:
// 1. Click Policies -> Create, fill fields:
// Name : Policy1
// Action : iot:*
// Resource ARN: *
// Effect : allow
// then, click "Create"
// 2. Click Manage -> Things -> Create things -> Create single thing -> Next
// Thing name: t1, no shadow, Next
// Auto-generate new certificate, Next
// Select policy Policy1, Create thing
// 3. From the dialog box that appears, download:
// xxx-certificate.pem.crt as cert.pem to the example directory
// xxx-private.pem.key as key.pem to the example directory
static const char *s_cert = "cert.pem";
static const char *s_key = "key.pem";
static const char *s_rx_topic = "d/rx";
static const char *s_tx_topic = "d/tx";
static int s_qos = 1;
#include "mongoose.h"
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_OPEN) {
// c->is_hexdumping = 1;
} else if (ev == MG_EV_ERROR) {
// On error, log error message
MG_ERROR(("%p %s", c->fd, (char *) ev_data));
} else if (ev == MG_EV_CONNECT) {
// Set up 2-way TLS that is required by AWS IoT
struct mg_tls_opts opts = {
.ca = "ca.pem", .cert = s_cert, .certkey = s_key};
mg_tls_init(c, &opts);
} else if (ev == MG_EV_MQTT_OPEN) {
// MQTT connect is successful
struct mg_str topic = mg_str(s_rx_topic);
MG_INFO(("Connected to %s", s_url));
MG_INFO(("Subscribing to %s", s_rx_topic));
mg_mqtt_sub(c, topic, s_qos);
c->label[0] = 'X'; // Set a label that we're logged in
} else if (ev == MG_EV_MQTT_MSG) {
// When we receive MQTT message, print it
struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data;
MG_INFO(("Received on %.*s : %.*s", (int) mm->topic.len, mm->topic.ptr,
(int) mm->data.len, mm->data.ptr));
} else if (ev == MG_EV_POLL && c->label[0] == 'X') {
static unsigned long prev_second;
unsigned long now_second = (*(unsigned long *) ev_data) / 1000;
if (now_second != prev_second) {
struct mg_str topic = mg_str(s_tx_topic), data = mg_str("{\"a\":123}");
MG_INFO(("Publishing to %s", s_tx_topic));
mg_mqtt_pub(c, topic, data, s_qos, false);
prev_second = now_second;
}
}
if (ev == MG_EV_ERROR || ev == MG_EV_CLOSE) {
MG_INFO(("Got event %d, stopping...", ev));
*(bool *) fn_data = true; // Signal that we're done
}
}
int main(void) {
struct mg_mgr mgr;
struct mg_mqtt_opts opts = {.clean = true};
bool done = false;
mg_mgr_init(&mgr); // Initialise event manager
MG_INFO(("Connecting to %s", s_url)); // Inform that we're starting
mg_mqtt_connect(&mgr, s_url, &opts, fn, &done); // Create client connection
while (!done) mg_mgr_poll(&mgr, 1000); // Loop until done
mg_mgr_free(&mgr); // Finished, cleanup
return 0;
}

Some files were not shown because too many files have changed in this diff Show More