mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-09 00:00:04 +01:00
fulltext search
This commit is contained in:
parent
602b0934c8
commit
d3bc2c3f4f
35 changed files with 35405 additions and 9 deletions
|
@ -43,6 +43,7 @@ option(LWS_WITH_HTTP_STREAM_COMPRESSION "Support HTTP stream compression" OFF)
|
|||
option(LWS_WITH_HTTP_BROTLI "Also offer brotli http stream compression (requires LWS_WITH_HTTP_STREAM_COMPRESSION)" OFF)
|
||||
option(LWS_WITH_ACME "Enable support for ACME automatic cert acquisition + maintenance (letsencrypt etc)" OFF)
|
||||
option(LWS_WITH_HUBBUB "Enable libhubbub rewriting support" OFF)
|
||||
option(LWS_WITH_FTS "Full Text Search support" ON)
|
||||
#
|
||||
# TLS library options... all except mbedTLS are basically OpenSSL variants.
|
||||
#
|
||||
|
@ -132,6 +133,7 @@ if(LWS_WITH_DISTRO_RECOMMENDED)
|
|||
set(LWS_WITH_LIBEVENT 0)
|
||||
set(LWS_WITHOUT_EXTENSIONS 0)
|
||||
set(LWS_ROLE_DBUS 1)
|
||||
set(LWS_WITH_FTS 1)
|
||||
endif()
|
||||
|
||||
# do you care about this? Then send me a patch where it disables it on travis
|
||||
|
@ -152,6 +154,7 @@ endif()
|
|||
|
||||
if (LWS_WITH_ESP32)
|
||||
set(LWS_WITH_LWSAC 0)
|
||||
set(LWS_WITH_FTS 0)
|
||||
endif()
|
||||
|
||||
project(libwebsockets C)
|
||||
|
@ -239,7 +242,7 @@ if (LWS_WITH_HTTP2 AND LWS_WITHOUT_SERVER)
|
|||
endif()
|
||||
|
||||
|
||||
include_directories(include)
|
||||
include_directories(include plugins)
|
||||
|
||||
|
||||
if (LWS_WITH_LWSWS)
|
||||
|
@ -886,6 +889,12 @@ if (LWS_WITH_LWSAC)
|
|||
lib/misc/lwsac/cached-file.c)
|
||||
endif()
|
||||
|
||||
if (LWS_WITH_FTS)
|
||||
list(APPEND SOURCES
|
||||
lib/misc/fts/trie.c
|
||||
lib/misc/fts/trie-fd.c)
|
||||
endif()
|
||||
|
||||
if (NOT LWS_WITHOUT_CLIENT)
|
||||
list(APPEND SOURCES
|
||||
lib/core/connect.c
|
||||
|
@ -1941,6 +1950,12 @@ endif()
|
|||
create_plugin(protocol_post_demo ""
|
||||
"plugins/protocol_post_demo.c" "" "")
|
||||
|
||||
if (LWS_WITH_FTS)
|
||||
create_plugin(protocol_fulltext_demo ""
|
||||
"plugins/protocol_fulltext_demo.c" "" "")
|
||||
endif()
|
||||
|
||||
|
||||
if (LWS_WITH_SSL)
|
||||
create_plugin(protocol_lws_ssh_base "plugins/ssh-base/include"
|
||||
"plugins/ssh-base/sshd.c;plugins/ssh-base/telnet.c;plugins/ssh-base/kex-25519.c" "plugins/ssh-base/crypto/chacha.c;plugins/ssh-base/crypto/ed25519.c;plugins/ssh-base/crypto/fe25519.c;plugins/ssh-base/crypto/ge25519.c;plugins/ssh-base/crypto/poly1305.c;plugins/ssh-base/crypto/sc25519.c;plugins/ssh-base/crypto/smult_curve25519_ref.c" "")
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#cmakedefine LWS_ROLE_DBUS
|
||||
|
||||
#cmakedefine LWS_WITH_LWSAC
|
||||
#cmakedefine LWS_WITH_FTS
|
||||
|
||||
/* Define to 1 to use wolfSSL/CyaSSL as a replacement for OpenSSL.
|
||||
* LWS_OPENSSL_SUPPORT needs to be set also for this to work. */
|
||||
|
|
63
doc-assets/lws-fts.svg
Normal file
63
doc-assets/lws-fts.svg
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="187.45mm" height="114.8mm" version="1.1" viewBox="0 0 187.4528 114.80323" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="b" overflow="visible" orient="auto">
|
||||
<path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
|
||||
</marker>
|
||||
<marker id="c" overflow="visible" orient="auto">
|
||||
<path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
|
||||
</marker>
|
||||
<marker id="a" overflow="visible" orient="auto">
|
||||
<path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
|
||||
</marker>
|
||||
<marker id="d" overflow="visible" orient="auto">
|
||||
<path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
|
||||
</marker>
|
||||
<marker id="f" overflow="visible" orient="auto">
|
||||
<path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
|
||||
</marker>
|
||||
<marker id="e" overflow="visible" orient="auto">
|
||||
<path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
|
||||
</marker>
|
||||
<marker id="g" overflow="visible" orient="auto">
|
||||
<path transform="matrix(-.2 0 0 -.2 -1.2 0)" d="m0 0 5-5-17.5 5 17.5 5z" fill-rule="evenodd" stroke="#000" stroke-width="1pt"/>
|
||||
</marker>
|
||||
<filter id="h" x="-.018221" y="-.030454" width="1.0364" height="1.0609" color-interpolation-filters="sRGB">
|
||||
<feGaussianBlur stdDeviation="1.0113078"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<g transform="translate(223.75 183.73)">
|
||||
<g>
|
||||
<rect transform="matrix(1.3577 0 0 1.3577 157.53 159.82)" x="-278.39" y="-250.61" width="133.21" height="79.7" filter="url(#h)"/>
|
||||
<rect x="-221.47" y="-181.71" width="180.86" height="108.21" fill="#fff"/>
|
||||
<rect x="-136.12" y="-131.41" width="66.299" height="13.971" fill="#c8c4b7"/>
|
||||
<circle transform="rotate(-90)" cx="99.407" cy="-188.19" r="13.209" fill="#5f8dd3" opacity=".742"/>
|
||||
<text x="-188.80128" y="-102.71894" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="5.1903px" letter-spacing="0px" stroke-width=".27805" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-188.80128" y="-102.71894">Original</tspan><tspan x="-188.80128" y="-96.231033">Text</tspan><tspan x="-188.80128" y="-89.743126">file</tspan></text>
|
||||
<circle transform="rotate(-90)" cx="128.11" cy="-199.63" r="13.209" fill="#5f8dd3" opacity=".742"/>
|
||||
<text x="-199.72415" y="-131.67715" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="5.1903px" letter-spacing="0px" stroke-width=".27805" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-199.72415" y="-131.67715">Original</tspan><tspan x="-199.72415" y="-125.18925">Text</tspan><tspan x="-199.72415" y="-118.70134">file</tspan></text>
|
||||
<circle transform="rotate(-90)" cx="156.82" cy="-184.64" r="13.209" fill="#5f8dd3" opacity=".742"/>
|
||||
<text x="-184.48293" y="-161.14342" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="5.1903px" letter-spacing="0px" stroke-width=".27805" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-184.48293" y="-161.14342">Original</tspan><tspan x="-184.48293" y="-154.65552">Text</tspan><tspan x="-184.48293" y="-148.1676">file</tspan></text>
|
||||
<circle transform="rotate(-90)" cx="126.08" cy="-154.16" r="23.116" fill="#217867"/>
|
||||
<text x="-154.524" y="-127.70548" dominant-baseline="auto" fill="#d7f4ee" font-family="'Open Sans'" font-size="13.246px" letter-spacing="0px" stroke-width=".70963" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-154.524" y="-127.70548">Index</tspan><tspan x="-154.524" y="-111.14747">File</tspan></text>
|
||||
</g>
|
||||
<g fill="none" stroke="#000" stroke-width="1.1745">
|
||||
<path d="m-177.53-107.03c6.6045-7.3666 6.6045-7.3666 6.6045-7.3666" marker-end="url(#g)"/>
|
||||
<path d="m-175.59-147.47 6.8585 7.3666" marker-end="url(#e)"/>
|
||||
<path d="m-186.9-127.65 11.939 0.63506" marker-end="url(#f)"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect x="-113.26" y="-146.65" width="27.434" height="41.151" fill="#fca"/>
|
||||
<text x="-99.287437" y="-128.11143" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="6.7058px" letter-spacing="0px" stroke-width=".35924" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-99.287437" y="-128.11143">Search</tspan><tspan x="-99.287437" y="-119.72922">Action</tspan></text>
|
||||
<text x="-83.03019" y="-161.13399" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="6.7058px" letter-spacing="0px" stroke-width=".35924" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-83.03019" y="-161.13399" stroke-width=".35924">Keyword</tspan></text>
|
||||
<rect x="-79.982" y="-146.91" width="27.434" height="41.151" fill="#fca"/>
|
||||
<text x="-65.248817" y="-131.4137" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="6.7058px" letter-spacing="0px" stroke-width=".35924" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-65.248817" y="-131.4137">Auto-</tspan><tspan x="-65.248817" y="-123.03148">comp-</tspan><tspan x="-65.248817" y="-114.64926">lete</tspan></text>
|
||||
<text x="-82.307457" y="-85.980392" dominant-baseline="auto" fill="#000000" font-family="'Open Sans'" font-size="6.7058px" letter-spacing="0px" stroke-width=".35924" text-align="center" text-anchor="middle" word-spacing="0px" style="font-feature-settings:normal;font-variant-alternates:normal;font-variant-caps:normal;font-variant-ligatures:normal;font-variant-numeric:normal;font-variant-position:normal;line-height:1.25;shape-padding:0;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-orientation:mixed;text-transform:none;white-space:normal" xml:space="preserve"><tspan x="-82.307457" y="-85.980392" stroke-width=".35924">Result lwsac</tspan></text>
|
||||
</g>
|
||||
<g fill="none" stroke="#000" stroke-width="1.1745">
|
||||
<path d="m-73.75-156.75 6.8585 7.3666" marker-end="url(#d)"/>
|
||||
<path d="m-89.77-156.75-6.8585 7.3666" marker-end="url(#a)"/>
|
||||
<path d="m-98.136-103.91 6.8585 7.3666" marker-end="url(#c)"/>
|
||||
<path d="m-65.384-104.42-6.8585 7.3666" marker-end="url(#b)"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 9 KiB |
|
@ -410,6 +410,7 @@ struct lws;
|
|||
#include <libwebsockets/lws-threadpool.h>
|
||||
#include <libwebsockets/lws-tokenize.h>
|
||||
#include <libwebsockets/lws-lwsac.h>
|
||||
#include <libwebsockets/lws-fts.h>
|
||||
|
||||
#if defined(LWS_WITH_TLS)
|
||||
|
||||
|
|
|
@ -404,7 +404,7 @@ lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason,
|
|||
buf[0] = '\0';
|
||||
lws_get_peer_simple(parent, buf, sizeof(buf));
|
||||
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_X_FORWARDED_FOR,
|
||||
(unsigned char *)buf, strlen(buf), p, end))
|
||||
(unsigned char *)buf, (int)strlen(buf), p, end))
|
||||
return -1;
|
||||
|
||||
break;
|
||||
|
|
|
@ -520,7 +520,7 @@ lws_callback_on_writable_all_protocol_vhost(const struct lws_vhost *vhost,
|
|||
return -1;
|
||||
}
|
||||
|
||||
n = protocol - vhost->protocols;
|
||||
n = (int)(protocol - vhost->protocols);
|
||||
|
||||
lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1,
|
||||
vhost->same_vh_protocol_heads[n].next) {
|
||||
|
|
318
lib/misc/fts/README.md
Normal file
318
lib/misc/fts/README.md
Normal file
|
@ -0,0 +1,318 @@
|
|||
# LWS Full Text Search
|
||||
|
||||
## Introduction
|
||||
|
||||

|
||||
|
||||
The general approach is to scan one or more UTF-8 input text "files" (they may
|
||||
only exist in memory) and create an in-memory optimized trie for every token in
|
||||
the file.
|
||||
|
||||
This can then be serialized out to disk in the form of a single index file (no
|
||||
matter how many input files were involved or how large they were).
|
||||
|
||||
The implementation is designed to be modest on memory and cpu for both index
|
||||
creation and querying, and suitable for weak machines with some kind of random
|
||||
access storage. For searching only memory to hold results is required, the
|
||||
actual searches and autocomplete suggestions are done very rapidly by seeking
|
||||
around structures in the on-disk index file.
|
||||
|
||||
Function|Related Link
|
||||
---|---
|
||||
Public API|[include/libwebsockets/lws-fts.h](
|
||||
https://libwebsockets.org/git/libwebsockets/tree/include/libwebsockets/lws-fts.h)
|
||||
CI test app|[minimal-examples/api-tests/api-test-fts](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/api-tests/api-test-fts)
|
||||
Demo minimal example|[minimal-examples/http-server/minimal-http-server-fulltext-search](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/http-server/minimal-http-server-fulltext-search)
|
||||
Live Demo|[https://libwebsockets.org/ftsdemo/](https://libwebsockets.org/ftsdemo/)
|
||||
|
||||
## Query API overview
|
||||
|
||||
Searching returns a potentially very large lwsac allocated object, with contents
|
||||
and max size controlled by the members of a struct lws_fts_search_params passed
|
||||
to the search function. Three kinds of result are possible:
|
||||
|
||||
### Autocomplete suggestions
|
||||
|
||||
These are useful to provide lists of extant results in
|
||||
realtime as the user types characters that constrain the search. So if the
|
||||
user has typed 'len', any hits for 'len' itself are reported along with
|
||||
'length', and whatever else is in the index beginning 'len'.. The results are
|
||||
selected using and are accompanied by an aggregated count of results down that
|
||||
path, and the results so the "most likely" results already measured by potential
|
||||
hits appear first.
|
||||
|
||||
These results are in a linked-list headed by `result.autocomplete_head` and
|
||||
each is in a `struct lws_fts_result_autocomplete`.
|
||||
|
||||
They're enabled in the search results by giving the flag
|
||||
`LWSFTS_F_QUERY_AUTOCOMPLETE` in the search parameter flags.
|
||||
|
||||
### Filepath results
|
||||
|
||||
Simply a list of input files containing the search term with some statistics,
|
||||
one file is mentioned in a `struct lws_fts_result_filepath` result struct.
|
||||
|
||||
This would be useful for creating a selection UI to "drill down" to individual
|
||||
files when there are many with matches.
|
||||
|
||||
This is enabled by the `LWSFTS_F_QUERY_FILES` search flag.
|
||||
|
||||
### Filepath and line results
|
||||
|
||||
Same as the file path list, but for each filepath, information on the line
|
||||
numbers and input file offset where the line starts are provided.
|
||||
|
||||
This is enabled by `LWSFTS_F_QUERY_FILE_LINES`... if you additionally give
|
||||
`LWSFTS_F_QUERY_QUOTE_LINE` flag then the contents of each hit line from the
|
||||
input file are also provided.
|
||||
|
||||
## Result format inside the lwsac
|
||||
|
||||
A `struct lws_fts_result` at the start of the lwsac contains heads for linked-
|
||||
lists of autocomplete and filepath results inside the lwsac.
|
||||
|
||||
For autocomplete suggestions, the string itself is immediately after the
|
||||
`struct lws_fts_result_autocomplete` in memory. For filepath results, after
|
||||
each `struct lws_fts_result_filepath` is
|
||||
|
||||
- match information depending on the flags given to the search
|
||||
- the filepath string
|
||||
|
||||
You can always skip the line number table to get the filepath string by adding
|
||||
.matches_length to the address of the byte after the struct.
|
||||
|
||||
The matches information is either
|
||||
|
||||
- 0 bytes per match
|
||||
|
||||
- 2x int32_t per match (8 bytes) if `LWSFTS_F_QUERY_FILE_LINES` given... the
|
||||
first is the native-endian line number of the match, the second is the
|
||||
byte offset in the original file where that line starts
|
||||
|
||||
- 2 x int32_t as above plus a const char * if `LWSFTS_F_QUERY_QUOTE_LINE` is
|
||||
also given... this points to a NUL terminated string also stored in the
|
||||
results lwsac that contains up to 255 chars of the line from the original
|
||||
file. In some cases, the original file was either virtual (you are indexing
|
||||
a git revision) or is not stored with the index, in that case you can't
|
||||
usefully use `LWSFTS_F_QUERY_QUOTE_LINE`.
|
||||
|
||||
To facilitate interpreting what is stored per match, the original search flags
|
||||
that created the result are stored in the `struct lws_fts_result`.
|
||||
|
||||
## Indexing In-memory and serialized to file
|
||||
|
||||
When creating the trie, in-memory structs are used with various optimization
|
||||
schemes trading off memory usage for speed. While in-memory, it's possible to
|
||||
add more indexed filepaths to the single index. Once the trie is complete in
|
||||
terms of having indexed everything, it is serialized to disk.
|
||||
|
||||
These contain many additional housekeeping pointers and trie entries which can
|
||||
be optimized out. Most in-memory values must be held literally in large types,
|
||||
whereas most of the values in the serialized file use smaller VLI which use
|
||||
more or less bytes according to the value. So the peak memory requirements for
|
||||
large tries are much bigger than the size of the serialized trie file that is
|
||||
output.
|
||||
|
||||
For the linux kernel at 4.14 and default indexing whitelist on a 2.8GHz AMD
|
||||
threadripper (using one thread), the stats are:
|
||||
|
||||
Name|Value
|
||||
---|---
|
||||
Files indexed|52932
|
||||
Input corpus size|694MiB
|
||||
Indexing cpu time|50.1s (>1000 files / sec; 13.8MBytes/sec)
|
||||
Peak alloc|78MiB
|
||||
Serialization time|202ms
|
||||
Trie File size|347MiB
|
||||
|
||||
To index libwebsockets master under the same conditions:
|
||||
|
||||
Name|Value
|
||||
---|---
|
||||
Files indexed|489
|
||||
Input corpus size|3MiB
|
||||
Indexing time|123ms
|
||||
Peak alloc|3MiB
|
||||
Serialization time|1ms
|
||||
Trie File size|1.4MiB
|
||||
|
||||
So the peak memory requirement to generate the whole trie in memory for that
|
||||
first is around 4x the size of the final output file.
|
||||
|
||||
Once it's generated, querying the trie file is very inexpensive, even when there
|
||||
are lots of results.
|
||||
|
||||
- trie entry child lists are kept sorted by the character they map to. This
|
||||
allows discovering there is no match as soon as a character later in the
|
||||
order than the one being matched is seen
|
||||
|
||||
- for the root trie, in addition to the linked-list child + sibling entries,
|
||||
a 256-entry pointer table is associated with the root trie, allowing one-
|
||||
step lookup. But as the table is 2KiB, it's too expensive to use on all
|
||||
trie entries
|
||||
|
||||
## Structure on disk
|
||||
|
||||
All explicit multibyte numbers are stored in Network (MSB-first) byte order.
|
||||
|
||||
- file header
|
||||
- filepath line number tables
|
||||
- filepath information
|
||||
- filepath map table
|
||||
- tries, trie instances (hits), trie child tables
|
||||
|
||||
### VLI coding
|
||||
|
||||
VLI (Variable Length Integer) coding works like this
|
||||
|
||||
[b7 EON] [b6 .. b0 DATA]
|
||||
|
||||
If EON = 0, then DATA represents the Least-significant 7 bits of the number.
|
||||
if EON = 1, DATA represents More-significant 7-bits that should be shifted
|
||||
left until the byte with EON = 0 is found to terminate the number.
|
||||
|
||||
The VLI used is predicated around 32-bit unsigned integers
|
||||
|
||||
Examples:
|
||||
|
||||
- 0x30 = 48
|
||||
- 0x81 30 = 176
|
||||
- 0x81 0x80 0x00 = 16384
|
||||
|
||||
Bytes | Range
|
||||
---|---
|
||||
1|<= 127
|
||||
2|<= 16K - 1
|
||||
3|<= 2M -1
|
||||
4|<= 256M - 1
|
||||
5|<= 4G - 1
|
||||
|
||||
The coding is very efficient if there's a high probabilty the number being
|
||||
stored is not large. So it's great for line numbers for example, where most
|
||||
files have less that 16K lines and the VLI for the line number fits in 2 bytes,
|
||||
but if you meet a huge file, the VLI coding can also handle it.
|
||||
|
||||
All numbers except a few in the headers that are actually written after the
|
||||
following data are stored using VLI for space- efficiency without limiting
|
||||
capability. The numbers that are fixed up after the fact have to have a fixed
|
||||
size and can't use VLI.
|
||||
|
||||
### File header
|
||||
|
||||
The first byte of the file header where the magic is, is "fileoffset" 0. All
|
||||
the stored "fileoffset"s are relative to that.
|
||||
|
||||
The header has a fixed size of 16 bytes.
|
||||
|
||||
size|function
|
||||
---|---
|
||||
32-bits|Magic 0xCA7A5F75
|
||||
32-bits|Fileoffset to root trie entry
|
||||
32-bits|Size of the trie file when it was created (to detect truncation)
|
||||
32-bits|Fileoffset to the filepath map
|
||||
32-bits|Number of filepaths
|
||||
|
||||
### Filepath line tables
|
||||
|
||||
Immediately after the file header are the line length tables.
|
||||
|
||||
As the input files are parsed, line length tables are written for each file...
|
||||
at that time the rest of the parser data is held in memory so nothing else is
|
||||
in the file yet. These allow you to map logical line numbers in the file to
|
||||
file offsets space- and time- efficiently without having to walk through the
|
||||
file contents.
|
||||
|
||||
The line information is cut into blocks, allowing quick skipping over the VLI
|
||||
data that doesn't contain the line you want just by following the 8-byte header
|
||||
part.
|
||||
|
||||
Once you find the block with your line, you have to iteratively add the VLIs
|
||||
until you hit the one you want.
|
||||
|
||||
For normal text files with average line length below 128, the VLIs will
|
||||
typically be a single byte. So a block of 200 line lengths is typically
|
||||
208 bytes long.
|
||||
|
||||
There is a final linetable chunk consisting of all zeros to indicate the end
|
||||
of the filepath line chunk series for a filepath.
|
||||
|
||||
size|function
|
||||
---|---
|
||||
16-bit|length of this chunk itself in bytes
|
||||
16-bit|count of lines covered in this chunk
|
||||
32-bit|count of bytes in the input file this chunk covers
|
||||
VLI...|for each line in the chunk, the number of bytes in the line
|
||||
|
||||
|
||||
### Filepaths
|
||||
|
||||
The single trie in the file may contain information from multiple files, for
|
||||
example one trie may cover all files in a directory. The "Filepaths" are
|
||||
listed after the line tables, and referred to by index thereafter.
|
||||
|
||||
For each filepath, one after the other:
|
||||
|
||||
size|function
|
||||
---|---
|
||||
VLI|fileoffset of the start of this filepath's line table
|
||||
VLI|count of lines in the file
|
||||
VLI|length of filepath in bytes
|
||||
...|the filepath (with no NUL)
|
||||
|
||||
### Filepath map
|
||||
|
||||
To facilitate rapid filepath lookup, there's a filepath map table with a 32-bit
|
||||
fileoffset per filepath. This is the way to convert filepath indexes to
|
||||
information on the filepath like its name, etc
|
||||
|
||||
size|function
|
||||
---|---
|
||||
32-bit...|fileoffset to filepath table for each filepath
|
||||
|
||||
### Trie entries
|
||||
|
||||
Immediately after that, the trie entries are dumped, for each one a header:
|
||||
|
||||
#### Trie entry header
|
||||
|
||||
size|function
|
||||
---|---
|
||||
VLI|Fileoffset of first file table in this trie entry instance list
|
||||
VLI|number of child trie entries this trie entry has
|
||||
VLI|number of instances this trie entry has
|
||||
|
||||
The child list follows immediately after this header
|
||||
|
||||
#### Trie entry instance file
|
||||
|
||||
For each file that has instances of this symbol:
|
||||
|
||||
size|function
|
||||
---|---
|
||||
VLI|Fileoffset of next file table in this trie entry instance list
|
||||
VLI|filepath index
|
||||
VLI|count of line number instances following
|
||||
|
||||
#### Trie entry file line number table
|
||||
|
||||
Then for the file mentioned above, a list of all line numbers in the file with
|
||||
the symbol in them, in ascending order. As a VLI, the median size per entry
|
||||
will typically be ~15.9 bits due to the probability of line numbers below 16K.
|
||||
|
||||
size|function
|
||||
---|---
|
||||
VLI|line number
|
||||
...
|
||||
|
||||
#### Trie entry child table
|
||||
|
||||
For each child node
|
||||
|
||||
size|function
|
||||
---|---
|
||||
VLI|file offset of child
|
||||
VLI|instance count belonging directly to this child
|
||||
VLI|aggregated number of instances down all descendent paths of child
|
||||
VLI|aggregated number of children down all descendent paths of child
|
||||
VLI|match string length
|
||||
...|the match string
|
23
lib/misc/fts/private.h
Normal file
23
lib/misc/fts/private.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
#include <libwebsockets.h>
|
||||
|
||||
/* if you need > 2GB trie files */
|
||||
//typedef off_t jg2_file_offset;
|
||||
typedef uint32_t jg2_file_offset;
|
||||
|
||||
struct lws_fts_file {
|
||||
int fd;
|
||||
jg2_file_offset root, flen, filepath_table;
|
||||
int max_direct_hits;
|
||||
int max_completion_hits;
|
||||
int filepaths;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#define TRIE_FILE_HDR_SIZE 20
|
||||
#define MAX_VLI 5
|
||||
|
||||
#define LWS_FTS_LINES_PER_CHUNK 200
|
||||
|
||||
int
|
||||
rq32(unsigned char *b, uint32_t *d);
|
1368
lib/misc/fts/trie.c
Normal file
1368
lib/misc/fts/trie.c
Normal file
File diff suppressed because it is too large
Load diff
|
@ -69,7 +69,8 @@ lws_plat_write_cert(struct lws_vhost *vhost, int is_key, int fd, void *buf,
|
|||
n = write(fd, buf, len);
|
||||
|
||||
fsync(fd);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
if (lseek(fd, 0, SEEK_SET) < 0)
|
||||
return 1;
|
||||
|
||||
return n != len;
|
||||
}
|
||||
|
|
|
@ -1233,9 +1233,9 @@ lws_http_action(struct lws *wsi)
|
|||
pcolon = NULL;
|
||||
|
||||
if (pcolon)
|
||||
n = pcolon - hit->origin;
|
||||
n = (int)(pcolon - hit->origin);
|
||||
else
|
||||
n = pslash - hit->origin;
|
||||
n = (int)(pslash - hit->origin);
|
||||
|
||||
if (n >= (int)sizeof(ads) - 2)
|
||||
n = sizeof(ads) - 2;
|
||||
|
@ -1270,7 +1270,8 @@ lws_http_action(struct lws *wsi)
|
|||
}
|
||||
|
||||
*p++ = '?';
|
||||
lws_hdr_copy(wsi, p, &rpath[sizeof(rpath) - 1] - p,
|
||||
lws_hdr_copy(wsi, p,
|
||||
(int)(&rpath[sizeof(rpath) - 1] - p),
|
||||
WSI_TOKEN_HTTP_URI_ARGS);
|
||||
while (--na) {
|
||||
if (*p == '\0')
|
||||
|
|
76
minimal-examples/api-tests/api-test-fts/CMakeLists.txt
Normal file
76
minimal-examples/api-tests/api-test-fts/CMakeLists.txt
Normal file
|
@ -0,0 +1,76 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
include(CheckCSourceCompiles)
|
||||
|
||||
set(SAMP lws-api-test-fts)
|
||||
set(SRCS main.c)
|
||||
|
||||
# If we are being built as part of lws, confirm current build config supports
|
||||
# reqconfig, else skip building ourselves.
|
||||
#
|
||||
# If we are being built externally, confirm installed lws was configured to
|
||||
# support reqconfig, else error out with a helpful message about the problem.
|
||||
#
|
||||
MACRO(require_lws_config reqconfig _val result)
|
||||
|
||||
if (DEFINED ${reqconfig})
|
||||
if (${reqconfig})
|
||||
set (rq 1)
|
||||
else()
|
||||
set (rq 0)
|
||||
endif()
|
||||
else()
|
||||
set(rq 0)
|
||||
endif()
|
||||
|
||||
if (${_val} EQUAL ${rq})
|
||||
set(SAME 1)
|
||||
else()
|
||||
set(SAME 0)
|
||||
endif()
|
||||
|
||||
if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
|
||||
if (${_val})
|
||||
message("${SAMP}: skipping as lws being built without ${reqconfig}")
|
||||
else()
|
||||
message("${SAMP}: skipping as lws built with ${reqconfig}")
|
||||
endif()
|
||||
set(${result} 0)
|
||||
else()
|
||||
if (LWS_WITH_MINIMAL_EXAMPLES)
|
||||
set(MET ${SAME})
|
||||
else()
|
||||
CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
|
||||
if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
|
||||
set(HAS_${reqconfig} 0)
|
||||
else()
|
||||
set(HAS_${reqconfig} 1)
|
||||
endif()
|
||||
if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
|
||||
set(MET 1)
|
||||
else()
|
||||
set(MET 0)
|
||||
endif()
|
||||
endif()
|
||||
if (NOT MET)
|
||||
if (${_val})
|
||||
message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
|
||||
else()
|
||||
message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
ENDMACRO()
|
||||
|
||||
set(requirements 1)
|
||||
require_lws_config(LWS_WITH_FTS 1 requirements)
|
||||
|
||||
if (requirements)
|
||||
add_executable(${SAMP} ${SRCS})
|
||||
|
||||
if (websockets_shared)
|
||||
target_link_libraries(${SAMP} websockets_shared)
|
||||
add_dependencies(${SAMP} websockets_shared)
|
||||
else()
|
||||
target_link_libraries(${SAMP} websockets)
|
||||
endif()
|
||||
endif()
|
53
minimal-examples/api-tests/api-test-fts/README.md
Normal file
53
minimal-examples/api-tests/api-test-fts/README.md
Normal file
|
@ -0,0 +1,53 @@
|
|||
# lws api test fts
|
||||
|
||||
Demonstrates how to create indexes and perform full-text searches.
|
||||
|
||||
## build
|
||||
|
||||
```
|
||||
$ cmake . && make
|
||||
```
|
||||
|
||||
## usage
|
||||
|
||||
Commandline option|Meaning
|
||||
---|---
|
||||
-d <loglevel>|Debug verbosity in decimal, eg, -d15
|
||||
-c / --createindex|Create an index file, instead of searching
|
||||
-i / --index <file>|Use this file as the index
|
||||
|
||||
The two modes are:
|
||||
|
||||
- create an index: `--createindex inputfile [inputfile...]`
|
||||
|
||||
```
|
||||
$ ./lws-api-test-fts -c ./the-picture-of-dorian-gray.txt
|
||||
[2018/10/15 07:14:15:1175] USER: LWS API selftest: full-text search
|
||||
[2018/10/15 07:14:15:1531] NOTICE: lws_fts_serialize: index 1 files (0MiB) cpu time 32ms, alloc: 1024KiB + 1024KiB, serialize: 3ms, file: 325KiB
|
||||
```
|
||||
|
||||
- perform search[es]: `searchterm [searchterm...]`
|
||||
|
||||
```
|
||||
$ ./lws-api-test-fts b
|
||||
[2018/10/15 07:15:44:1442] USER: LWS API selftest: full-text search
|
||||
[2018/10/15 07:15:44:1442] NOTICE: lws_fts_search: 'b' Matched: 3 instances, 8 children, 0ms
|
||||
[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC b: 3 agg hits
|
||||
[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC be: 472 agg hits
|
||||
[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC bee: 3 agg hits
|
||||
[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC been: 236 agg hits
|
||||
[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC beaut: 1 agg hits
|
||||
[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC beauty: 55 agg hits
|
||||
[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC because: 40 agg hits
|
||||
[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC believe: 49 agg hits
|
||||
[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC better: 54 agg hits
|
||||
[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC before: 75 agg hits
|
||||
[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC beg: 5 agg hits
|
||||
[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC began: 44 agg hits
|
||||
[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC but: 401 agg hits
|
||||
[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC basil: 158 agg hits
|
||||
[2018/10/15 07:15:44:1443] NOTICE: lws_fts_results_dump: AC broke: 22 agg hits
|
||||
[2018/10/15 07:15:44:1444] NOTICE: lws_fts_results_dump: AC by: 242 agg hits
|
||||
[2018/10/15 07:15:44:1444] NOTICE: lws_fts_results_dump: AC boy: 36 agg hits
|
||||
```
|
||||
|
26
minimal-examples/api-tests/api-test-fts/canned-1.txt
Normal file
26
minimal-examples/api-tests/api-test-fts/canned-1.txt
Normal file
|
@ -0,0 +1,26 @@
|
|||
API selftest: full-text search
|
||||
AC be: 472 agg hits
|
||||
AC but: 401 agg hits
|
||||
AC by: 242 agg hits
|
||||
AC been: 236 agg hits
|
||||
AC basil: 158 agg hits
|
||||
AC before: 75 agg hits
|
||||
AC beauty: 55 agg hits
|
||||
AC better: 54 agg hits
|
||||
AC believe: 49 agg hits
|
||||
AC began: 44 agg hits
|
||||
AC because: 40 agg hits
|
||||
AC boy: 36 agg hits
|
||||
AC book: 31 agg hits
|
||||
AC body: 28 agg hits
|
||||
AC both: 26 agg hits
|
||||
AC broke: 22 agg hits
|
||||
AC beg: 5 agg hits
|
||||
AC bore: 5 agg hits
|
||||
AC b: 3 agg hits
|
||||
AC bee: 3 agg hits
|
||||
AC beaut: 1 agg hits
|
||||
no filepath results
|
||||
|
||||
|
||||
|
42
minimal-examples/api-tests/api-test-fts/canned-2.txt
Normal file
42
minimal-examples/api-tests/api-test-fts/canned-2.txt
Normal file
|
@ -0,0 +1,42 @@
|
|||
API selftest: full-text search
|
||||
no autocomplete results
|
||||
../minimal-examples/api-tests/api-test-fts/the-picture-of-dorian-gray.txt: (8904 lines) 32 hits
|
||||
360
|
||||
17482
|
||||
393
|
||||
18984
|
||||
562
|
||||
28820
|
||||
837
|
||||
42903
|
||||
1640
|
||||
82057
|
||||
2037
|
||||
102214
|
||||
2091
|
||||
105019
|
||||
2145
|
||||
107351
|
||||
2725
|
||||
137188
|
||||
2808
|
||||
141127
|
||||
2977
|
||||
149971
|
||||
3429
|
||||
173810
|
||||
4417
|
||||
229186
|
||||
4431
|
||||
230058
|
||||
4656
|
||||
241181
|
||||
4708
|
||||
244372
|
||||
../minimal-examples/api-tests/api-test-fts/les-mis-utf8.txt: (14399 lines) 3 hits
|
||||
14106
|
||||
14313
|
||||
14396
|
||||
|
||||
|
||||
|
14399
minimal-examples/api-tests/api-test-fts/les-mis-utf8.txt
Normal file
14399
minimal-examples/api-tests/api-test-fts/les-mis-utf8.txt
Normal file
File diff suppressed because it is too large
Load diff
222
minimal-examples/api-tests/api-test-fts/main.c
Normal file
222
minimal-examples/api-tests/api-test-fts/main.c
Normal file
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* lws-api-test-fts
|
||||
*
|
||||
* Copyright (C) 2018 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* This file is made available under the Creative Commons CC0 1.0
|
||||
* Universal Public Domain Dedication.
|
||||
*/
|
||||
|
||||
#include <libwebsockets.h>
|
||||
#include <getopt.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
static struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "createindex", no_argument, NULL, 'c' },
|
||||
{ "index", required_argument, NULL, 'i' },
|
||||
{ "debug", required_argument, NULL, 'd' },
|
||||
{ "file", required_argument, NULL, 'f' },
|
||||
{ "lines", required_argument, NULL, 'l' },
|
||||
{ NULL, 0, 0, 0 }
|
||||
};
|
||||
|
||||
static const char *index_filepath = "/tmp/lws-fts-test-index";
|
||||
static char filepath[256];
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int n, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
|
||||
int fd, fi, ft, createindex = 0, flags = LWSFTS_F_QUERY_AUTOCOMPLETE;
|
||||
struct lws_fts_search_params params;
|
||||
struct lws_fts_result *result;
|
||||
struct lws_fts_file *jtf;
|
||||
struct lws_fts *t;
|
||||
char buf[16384];
|
||||
|
||||
do {
|
||||
n = getopt_long(argc, argv, "hd:i:cfl", options, NULL);
|
||||
if (n < 0)
|
||||
continue;
|
||||
switch (n) {
|
||||
case 'i':
|
||||
strncpy(filepath, optarg, sizeof(filepath) - 1);
|
||||
filepath[sizeof(filepath) - 1] = '\0';
|
||||
index_filepath = filepath;
|
||||
break;
|
||||
case 'd':
|
||||
logs = atoi(optarg);
|
||||
break;
|
||||
case 'c':
|
||||
createindex = 1;
|
||||
break;
|
||||
case 'f':
|
||||
flags &= ~LWSFTS_F_QUERY_AUTOCOMPLETE;
|
||||
flags |= LWSFTS_F_QUERY_FILES;
|
||||
break;
|
||||
case 'l':
|
||||
flags |= LWSFTS_F_QUERY_FILES |
|
||||
LWSFTS_F_QUERY_FILE_LINES;
|
||||
break;
|
||||
case 'h':
|
||||
fprintf(stderr,
|
||||
"Usage: %s [--createindex]"
|
||||
"[--index=<index filepath>] "
|
||||
"[-d <log bitfield>] file1 file2 \n",
|
||||
argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
} while (n >= 0);
|
||||
|
||||
lws_set_log_level(logs, NULL);
|
||||
lwsl_user("LWS API selftest: full-text search\n");
|
||||
|
||||
if (createindex) {
|
||||
|
||||
lwsl_notice("Creating index\n");
|
||||
|
||||
/*
|
||||
* create an index by shifting through argv and indexing each
|
||||
* file given there into a single combined index
|
||||
*/
|
||||
|
||||
ft = open(index_filepath, O_CREAT | O_WRONLY | O_TRUNC, 0600);
|
||||
if (ft < 0) {
|
||||
lwsl_err("%s: can't open index %s\n", __func__,
|
||||
index_filepath);
|
||||
|
||||
goto bail;
|
||||
}
|
||||
|
||||
t = lws_fts_create(ft);
|
||||
if (!t) {
|
||||
lwsl_err("%s: Unable to allocate trie\n", __func__);
|
||||
|
||||
goto bail1;
|
||||
}
|
||||
|
||||
while (optind < argc) {
|
||||
|
||||
fi = lws_fts_file_index(t, argv[optind],
|
||||
strlen(argv[optind]), 1);
|
||||
if (fi < 0) {
|
||||
lwsl_err("%s: Failed to get file idx for %s\n",
|
||||
__func__, argv[optind]);
|
||||
|
||||
goto bail1;
|
||||
}
|
||||
|
||||
fd = open(argv[optind], O_RDONLY);
|
||||
if (fd < 0) {
|
||||
lwsl_err("unable to open %s for read\n",
|
||||
argv[optind]);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
do {
|
||||
int n = read(fd, buf, sizeof(buf));
|
||||
|
||||
if (n <= 0)
|
||||
break;
|
||||
|
||||
if (lws_fts_fill(t, fi, buf, n)) {
|
||||
lwsl_err("%s: lws_fts_fill failed\n",
|
||||
__func__);
|
||||
close(fd);
|
||||
|
||||
goto bail;
|
||||
}
|
||||
|
||||
} while (1);
|
||||
|
||||
close(fd);
|
||||
optind++;
|
||||
}
|
||||
|
||||
if (lws_fts_serialize(t)) {
|
||||
lwsl_err("%s: serialize failed\n", __func__);
|
||||
|
||||
goto bail;
|
||||
}
|
||||
|
||||
lws_fts_destroy(&t);
|
||||
close(ft);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* shift through argv searching for each token
|
||||
*/
|
||||
|
||||
jtf = lws_fts_open(index_filepath);
|
||||
if (!jtf)
|
||||
goto bail;
|
||||
|
||||
while (optind < argc) {
|
||||
|
||||
struct lws_fts_result_autocomplete *ac;
|
||||
struct lws_fts_result_filepath *fp;
|
||||
uint32_t *l, n;
|
||||
|
||||
memset(¶ms, 0, sizeof(params));
|
||||
|
||||
params.needle = argv[optind];
|
||||
params.flags = flags;
|
||||
params.max_autocomplete = 20;
|
||||
params.max_files = 20;
|
||||
|
||||
result = lws_fts_search(jtf, ¶ms);
|
||||
|
||||
if (!result) {
|
||||
lwsl_err("%s: search failed\n", __func__);
|
||||
lws_fts_close(jtf);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
ac = result->autocomplete_head;
|
||||
fp = result->filepath_head;
|
||||
|
||||
if (!ac)
|
||||
lwsl_notice("%s: no autocomplete results\n", __func__);
|
||||
|
||||
while (ac) {
|
||||
lwsl_notice("%s: AC %s: %d agg hits\n", __func__,
|
||||
((char *)(ac + 1)), ac->instances);
|
||||
|
||||
ac = ac->next;
|
||||
}
|
||||
|
||||
if (!fp)
|
||||
lwsl_notice("%s: no filepath results\n", __func__);
|
||||
|
||||
while (fp) {
|
||||
lwsl_notice("%s: %s: (%d lines) %d hits \n", __func__,
|
||||
(((char *)(fp + 1)) + fp->matches_length),
|
||||
fp->lines_in_file, fp->matches);
|
||||
|
||||
if (fp->matches_length) {
|
||||
l = (uint32_t *)(fp + 1);
|
||||
n = 0;
|
||||
while ((int)n++ < fp->matches)
|
||||
lwsl_notice(" %d\n", *l++);
|
||||
}
|
||||
fp = fp->next;
|
||||
}
|
||||
|
||||
lwsac_free(¶ms.results_head);
|
||||
|
||||
optind++;
|
||||
}
|
||||
|
||||
lws_fts_close(jtf);
|
||||
|
||||
return 0;
|
||||
|
||||
bail1:
|
||||
close(ft);
|
||||
bail:
|
||||
lwsl_user("FAILED\n");
|
||||
|
||||
return 1;
|
||||
}
|
58
minimal-examples/api-tests/api-test-fts/selftest.sh
Executable file
58
minimal-examples/api-tests/api-test-fts/selftest.sh
Executable file
|
@ -0,0 +1,58 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# $1: path to minimal example binaries...
|
||||
# if lws is built with -DLWS_WITH_MINIMAL_EXAMPLES=1
|
||||
# that will be ./bin from your build dir
|
||||
#
|
||||
# $2: path for logs and results. The results will go
|
||||
# in a subdir named after the directory this script
|
||||
# is in
|
||||
#
|
||||
# $3: offset for test index count
|
||||
#
|
||||
# $4: total test count
|
||||
#
|
||||
# $5: path to ./minimal-examples dir in lws
|
||||
#
|
||||
# Test return code 0: OK, 254: timed out, other: error indication
|
||||
|
||||
. $5/selftests-library.sh
|
||||
|
||||
COUNT_TESTS=4
|
||||
|
||||
FAILS=0
|
||||
|
||||
#
|
||||
# let's make an index with just Dorian first
|
||||
#
|
||||
dotest $1 $2 apitest -c -i /tmp/lws-fts-dorian.index \
|
||||
"../minimal-examples/api-tests/api-test-fts/the-picture-of-dorian-gray.txt"
|
||||
|
||||
# and let's hear about autocompletes for "b"
|
||||
|
||||
dotest $1 $2 apitest -i /tmp/lws-fts-dorian.index b
|
||||
cat $2/api-test-fts/apitest.log | cut -d' ' -f5- > /tmp/fts1
|
||||
diff -urN /tmp/fts1 "../minimal-examples/api-tests/api-test-fts/canned-1.txt"
|
||||
if [ $? -ne 0 ] ; then
|
||||
echo "Test 1 failed"
|
||||
FAILS=$(( $FAILS + 1 ))
|
||||
fi
|
||||
|
||||
#
|
||||
# let's make an index with Dorian + Les Mis in French (ie, UTF-8) as well
|
||||
#
|
||||
dotest $1 $2 apitest -c -i /tmp/lws-fts-both.index \
|
||||
"../minimal-examples/api-tests/api-test-fts/the-picture-of-dorian-gray.txt" \
|
||||
"../minimal-examples/api-tests/api-test-fts/les-mis-utf8.txt"
|
||||
|
||||
# and let's hear about "help", which appears in both
|
||||
|
||||
dotest $1 $2 apitest -i /tmp/lws-fts-both.index -f -l help
|
||||
cat $2/api-test-fts/apitest.log | cut -d' ' -f5- > /tmp/fts2
|
||||
diff -urN /tmp/fts2 "../minimal-examples/api-tests/api-test-fts/canned-2.txt"
|
||||
if [ $? -ne 0 ] ; then
|
||||
echo "Test 1 failed"
|
||||
FAILS=$(( $FAILS + 1 ))
|
||||
fi
|
||||
|
||||
exit $FAILS
|
File diff suppressed because it is too large
Load diff
|
@ -8,6 +8,7 @@ minimal-http-server-eventlib|Same as minimal-http-server but works with a suppor
|
|||
minimal-http-server-form-get|Process a GET form
|
||||
minimal-http-server-form-post-file|Process a multipart POST form with file transfer
|
||||
minimal-http-server-form-post|Process a POST form (no file transfer)
|
||||
minimal-http-server-fulltext-search|Demonstrates using lws Fulltext Search
|
||||
minimal-http-server-mimetypes|Shows how to add support for additional mimetypes at runtime
|
||||
minimal-http-server-multivhost|Same as minimal-http-server but three different vhosts
|
||||
minimal-http-server-proxy|Reverse Proxy
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
include(CheckCSourceCompiles)
|
||||
|
||||
set(SAMP lws-minimal-http-server-fulltext-search)
|
||||
set(SRCS minimal-http-server.c)
|
||||
|
||||
include_directories(../../../plugins)
|
||||
|
||||
# If we are being built as part of lws, confirm current build config supports
|
||||
# reqconfig, else skip building ourselves.
|
||||
#
|
||||
# If we are being built externally, confirm installed lws was configured to
|
||||
# support reqconfig, else error out with a helpful message about the problem.
|
||||
#
|
||||
MACRO(require_lws_config reqconfig _val result)
|
||||
|
||||
if (DEFINED ${reqconfig})
|
||||
if (${reqconfig})
|
||||
set (rq 1)
|
||||
else()
|
||||
set (rq 0)
|
||||
endif()
|
||||
else()
|
||||
set(rq 0)
|
||||
endif()
|
||||
|
||||
if (${_val} EQUAL ${rq})
|
||||
set(SAME 1)
|
||||
else()
|
||||
set(SAME 0)
|
||||
endif()
|
||||
|
||||
if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
|
||||
if (${_val})
|
||||
message("${SAMP}: skipping as lws being built without ${reqconfig}")
|
||||
else()
|
||||
message("${SAMP}: skipping as lws built with ${reqconfig}")
|
||||
endif()
|
||||
set(${result} 0)
|
||||
else()
|
||||
if (LWS_WITH_MINIMAL_EXAMPLES)
|
||||
set(MET ${SAME})
|
||||
else()
|
||||
CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
|
||||
if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
|
||||
set(HAS_${reqconfig} 0)
|
||||
else()
|
||||
set(HAS_${reqconfig} 1)
|
||||
endif()
|
||||
if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
|
||||
set(MET 1)
|
||||
else()
|
||||
set(MET 0)
|
||||
endif()
|
||||
endif()
|
||||
if (NOT MET)
|
||||
if (${_val})
|
||||
message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
|
||||
else()
|
||||
message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
endif()
|
||||
ENDMACRO()
|
||||
|
||||
|
||||
set(requirements 1)
|
||||
require_lws_config(LWS_ROLE_H1 1 requirements)
|
||||
require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
|
||||
|
||||
if (requirements)
|
||||
add_executable(${SAMP} ${SRCS})
|
||||
|
||||
if (websockets_shared)
|
||||
target_link_libraries(${SAMP} websockets_shared)
|
||||
add_dependencies(${SAMP} websockets_shared)
|
||||
else()
|
||||
target_link_libraries(${SAMP} websockets)
|
||||
endif()
|
||||
endif()
|
|
@ -0,0 +1,18 @@
|
|||
# lws minimal http server
|
||||
|
||||
## build
|
||||
|
||||
```
|
||||
$ cmake . && make
|
||||
```
|
||||
|
||||
## usage
|
||||
|
||||
```
|
||||
$ ./lws-minimal-http-server
|
||||
[2018/03/04 09:30:02:7986] USER: LWS minimal http server | visit http://localhost:7681
|
||||
[2018/03/04 09:30:02:7986] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 on
|
||||
```
|
||||
|
||||
Visit http://localhost:7681
|
||||
|
Binary file not shown.
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* lws-minimal-http-server-fts
|
||||
*
|
||||
* Copyright (C) 2018 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* This file is made available under the Creative Commons CC0 1.0
|
||||
* Universal Public Domain Dedication.
|
||||
*
|
||||
* This demonstrates how to use lws full-text search
|
||||
*/
|
||||
|
||||
#include <libwebsockets.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
|
||||
#define LWS_PLUGIN_STATIC
|
||||
#include <protocol_fulltext_demo.c>
|
||||
|
||||
const char *index_filepath = "./lws-fts.index";
|
||||
static int interrupted;
|
||||
|
||||
static struct lws_protocols protocols[] = {
|
||||
LWS_PLUGIN_PROTOCOL_FULLTEXT_DEMO,
|
||||
{ NULL, NULL, 0, 0 } /* terminator */
|
||||
};
|
||||
|
||||
static struct lws_protocol_vhost_options pvo_idx = {
|
||||
NULL,
|
||||
NULL,
|
||||
"indexpath", /* pvo name */
|
||||
NULL /* filled in at runtime */
|
||||
};
|
||||
|
||||
static const struct lws_protocol_vhost_options pvo = {
|
||||
NULL, /* "next" pvo linked-list */
|
||||
&pvo_idx, /* "child" pvo linked-list */
|
||||
"lws-test-fts", /* protocol name we belong to on this vhost */
|
||||
"" /* ignored */
|
||||
};
|
||||
|
||||
/* override the default mount for /fts in the URL space */
|
||||
|
||||
static const struct lws_http_mount mount_fts = {
|
||||
/* .mount_next */ NULL, /* linked-list "next" */
|
||||
/* .mountpoint */ "/fts", /* mountpoint URL */
|
||||
/* .origin */ NULL, /* protocol */
|
||||
/* .def */ NULL,
|
||||
/* .protocol */ "lws-test-fts",
|
||||
/* .cgienv */ NULL,
|
||||
/* .extra_mimetypes */ NULL,
|
||||
/* .interpret */ NULL,
|
||||
/* .cgi_timeout */ 0,
|
||||
/* .cache_max_age */ 0,
|
||||
/* .auth_mask */ 0,
|
||||
/* .cache_reusable */ 0,
|
||||
/* .cache_revalidate */ 0,
|
||||
/* .cache_intermediaries */ 0,
|
||||
/* .origin_protocol */ LWSMPRO_CALLBACK, /* dynamic */
|
||||
/* .mountpoint_len */ 4, /* char count */
|
||||
/* .basic_auth_login_file */ NULL,
|
||||
};
|
||||
|
||||
static const struct lws_http_mount mount = {
|
||||
/* .mount_next */ &mount_fts, /* linked-list "next" */
|
||||
/* .mountpoint */ "/", /* mountpoint URL */
|
||||
/* .origin */ "./mount-origin", /* serve from dir */
|
||||
/* .def */ "index.html", /* default filename */
|
||||
/* .protocol */ NULL,
|
||||
/* .cgienv */ NULL,
|
||||
/* .extra_mimetypes */ NULL,
|
||||
/* .interpret */ NULL,
|
||||
/* .cgi_timeout */ 0,
|
||||
/* .cache_max_age */ 0,
|
||||
/* .auth_mask */ 0,
|
||||
/* .cache_reusable */ 0,
|
||||
/* .cache_revalidate */ 0,
|
||||
/* .cache_intermediaries */ 0,
|
||||
/* .origin_protocol */ LWSMPRO_FILE, /* files in a dir */
|
||||
/* .mountpoint_len */ 1, /* char count */
|
||||
/* .basic_auth_login_file */ NULL,
|
||||
};
|
||||
|
||||
void sigint_handler(int sig)
|
||||
{
|
||||
interrupted = 1;
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
|
||||
struct lws_context_creation_info info;
|
||||
struct lws_context *context;
|
||||
const char *p;
|
||||
|
||||
signal(SIGINT, sigint_handler);
|
||||
|
||||
if ((p = lws_cmdline_option(argc, argv, "-d")))
|
||||
logs = atoi(p);
|
||||
|
||||
lws_set_log_level(logs, NULL);
|
||||
lwsl_user("LWS minimal http server fulltext search | "
|
||||
"visit http://localhost:7681\n");
|
||||
|
||||
memset(&info, 0, sizeof info);
|
||||
info.port = 7681;
|
||||
info.mounts = &mount;
|
||||
info.protocols = protocols;
|
||||
info.pvo = &pvo;
|
||||
|
||||
pvo_idx.value = index_filepath;
|
||||
|
||||
context = lws_create_context(&info);
|
||||
if (!context) {
|
||||
lwsl_err("lws init failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
while (n >= 0 && !interrupted)
|
||||
n = lws_service(context, 1000);
|
||||
|
||||
lws_context_destroy(context);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<meta charset="UTF-8">
|
||||
<html>
|
||||
<body>
|
||||
<img src="libwebsockets.org-logo.png"><br>
|
||||
<h1>404</h1>
|
||||
Sorry, that file doesn't exist.
|
||||
</body>
|
||||
</html>
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 166 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,29 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset=utf-8 http-equiv="Content-Language" content="en"/>
|
||||
<script src="lws-fts.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="lws-fts.css"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<img src="./libwebsockets.org-logo.png"><br>
|
||||
<div class="searchbg">
|
||||
|
||||
<span class="title">The Picture of Dorian Gray</span><br>
|
||||
|
||||
<table class="searchtable">
|
||||
<tr>
|
||||
<td rowspan='2'><img class='eyeglass'></td>
|
||||
<td class='searchboxtitle'>Fulltext search<br>
|
||||
<input type='text' id='lws_fts' class='nonviable'
|
||||
name='lws_fts' maxlength='80'>
|
||||
<div class='acomplete' id='acomplete'></div>
|
||||
</td></tr></table>
|
||||
|
||||
<div class='searchresults' id='searchresults'></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 6.9 KiB |
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,211 @@
|
|||
/* lws-fts.js - JS supporting lws fulltext search
|
||||
*
|
||||
* Copyright (C) 2018 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation:
|
||||
* version 2.1 of the License.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
var last_ac = "";
|
||||
|
||||
function san(s)
|
||||
{
|
||||
s.replace("<", "!");
|
||||
s.replace("%", "!");
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
function lws_fts_choose()
|
||||
{
|
||||
var xhr = new XMLHttpRequest();
|
||||
var sr = document.getElementById("searchresults");
|
||||
var ac = document.getElementById("acomplete");
|
||||
var inp = document.getElementById("lws_fts");
|
||||
|
||||
xhr.onopen = function(e) {
|
||||
xhr.setRequestHeader("cache-control", "max-age=0");
|
||||
};
|
||||
|
||||
xhr.onload = function(e) {
|
||||
var jj, n, m, s = "", x, lic = 0, hl, re;
|
||||
var sr = document.getElementById("searchresults");
|
||||
var ac = document.getElementById("acomplete");
|
||||
var inp = document.getElementById("lws_fts");
|
||||
sr.style.width = (parseInt(sr.parentNode.offsetWidth, 10) - 88) + "px";
|
||||
sr.style.opacity = "1";
|
||||
inp.blur();
|
||||
|
||||
hl = document.getElementById("lws_fts").value;
|
||||
re = new RegExp(hl, "gi");
|
||||
|
||||
// console.log(xhr.responseText);
|
||||
jj = JSON.parse(xhr.responseText);
|
||||
|
||||
if (jj.fp) {
|
||||
lic = jj.fp.length;
|
||||
for (n = 0; n < lic; n++) {
|
||||
var q;
|
||||
|
||||
s += "<div class='filepath'>" + jj.fp[n].path + "</div>";
|
||||
|
||||
s += "<table>";
|
||||
for (m = 0; m < jj.fp[n].hits.length; m++)
|
||||
s += "<tr><td class='r'>" + jj.fp[n].hits[m].l +
|
||||
"</td><td>" + jj.fp[n].hits[m].s +
|
||||
"</td></tr>";
|
||||
|
||||
s += "</table>";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
sr.innerHTML = s;
|
||||
};
|
||||
|
||||
inp.blur();
|
||||
ac.style.opacity = "0";
|
||||
sr.style.innerHTML = "";
|
||||
xhr.open("GET", "../fts/r/" + document.getElementById("lws_fts").value);
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function lws_fts_ac_select(e)
|
||||
{
|
||||
var t = e.target;
|
||||
|
||||
while (t) {
|
||||
if (t.getAttribute && t.getAttribute("string")) {
|
||||
document.getElementById("lws_fts").value =
|
||||
t.getAttribute("string");
|
||||
|
||||
lws_fts_choose();
|
||||
}
|
||||
|
||||
t = t.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
function lws_fts_search_input()
|
||||
{
|
||||
var ac = document.getElementById("acomplete"),
|
||||
sb = document.getElementById("lws_fts");
|
||||
|
||||
if (last_ac === sb.value)
|
||||
return;
|
||||
|
||||
last_ac = sb.value;
|
||||
|
||||
ac.style.width = (parseInt(sb.offsetWidth, 10) - 2) + "px";
|
||||
ac.style.opacity = "1";
|
||||
|
||||
/* detect loss of focus for popup menu */
|
||||
sb.addEventListener("focusout", function(e) {
|
||||
ac.style.opacity = "0";
|
||||
});
|
||||
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.onopen = function(e) {
|
||||
xhr.setRequestHeader("cache-control", "max-age=0");
|
||||
};
|
||||
xhr.onload = function(e) {
|
||||
var jj, n, s = "", x, lic = 0;
|
||||
var inp = document.getElementById("lws_fts");
|
||||
var ac = document.getElementById("acomplete");
|
||||
|
||||
// console.log(xhr.responseText);
|
||||
jj = JSON.parse(xhr.responseText);
|
||||
|
||||
switch(parseInt(jj.indexed, 10)) {
|
||||
case 0: /* there is no index */
|
||||
break;
|
||||
|
||||
case 1: /* yay there is an index */
|
||||
|
||||
if (jj.ac) {
|
||||
lic = jj.ac.length;
|
||||
s += "<ul id='menu-ul'>";
|
||||
for (n = 0; n < lic; n++) {
|
||||
|
||||
if (jj.ac[n] && parseInt(jj.ac[n].matches, 10))
|
||||
s += "<li id='mi_ac" + n + "' string='" +
|
||||
san(jj.ac[n].ac) +
|
||||
"'><table><tr><td>" + san(jj.ac[n].ac) +
|
||||
"</td><td class='r'>" +
|
||||
parseInt(jj.ac[n].matches, 10) +
|
||||
"</td></tr></table></li>";
|
||||
}
|
||||
|
||||
s += "</ul>";
|
||||
|
||||
if (!lic) {
|
||||
//s = "<img class='noentry'>";
|
||||
inp.className = "nonviable";
|
||||
ac.style.opacity = "0";
|
||||
} else {
|
||||
inp.className = "viable";
|
||||
ac.style.opacity = "1";
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
/* an index is being built... */
|
||||
|
||||
s = "<table><tr><td><img class='spinner'></td><td>" +
|
||||
"<table><tr><td>Indexing</td></tr><tr><td>" +
|
||||
"<div id='bar1' class='bar1'>" +
|
||||
"<div id='bar2' class='bar2'>" +
|
||||
jj.index_done + " / " + jj.index_files +
|
||||
"</div></div></td></tr></table>" +
|
||||
"</td></tr></table>";
|
||||
|
||||
setTimeout(lws_fts_search_input, 300);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
ac.innerHTML = s;
|
||||
|
||||
for (n = 0; n < lic; n++)
|
||||
if (document.getElementById("mi_ac" + n))
|
||||
document.getElementById("mi_ac" + n).
|
||||
addEventListener("click", lws_fts_ac_select);
|
||||
if (jj.index_files) {
|
||||
document.getElementById("bar2").style.width =
|
||||
((150 * jj.index_done) / (jj.index_files + 1)) + "px";
|
||||
}
|
||||
};
|
||||
|
||||
xhr.open("GET", "../fts/a/" + document.getElementById("lws_fts").value);
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
var inp = document.getElementById("lws_fts");
|
||||
|
||||
inp.addEventListener("input", lws_fts_search_input, false);
|
||||
|
||||
inp.addEventListener("keydown",
|
||||
function(e) {
|
||||
var inp = document.getElementById("lws_fts");
|
||||
var sr = document.getElementById("searchresults");
|
||||
var ac = document.getElementById("acomplete");
|
||||
if (e.key === "Enter" && inp.className === "viable") {
|
||||
lws_fts_choose();
|
||||
sr.focus();
|
||||
ac.style.opacity = "0";
|
||||
}
|
||||
}, false);
|
||||
|
||||
}, false);
|
||||
|
||||
}());
|
File diff suppressed because it is too large
Load diff
|
@ -57,7 +57,7 @@ dotest() {
|
|||
T=$3
|
||||
(
|
||||
{
|
||||
/usr/bin/time -p $1/lws-$MYTEST $4 $5 $6 $7 > $2/$MYTEST/$T.log 2> $2/$MYTEST/$T.log ;
|
||||
/usr/bin/time -p $1/lws-$MYTEST $4 $5 $6 $7 $8 $9 > $2/$MYTEST/$T.log 2> $2/$MYTEST/$T.log ;
|
||||
echo $? > $2/$MYTEST/$T.result
|
||||
} 2> $2/$MYTEST/$T.time >/dev/null
|
||||
) >/dev/null 2> /dev/null &
|
||||
|
|
293
plugins/protocol_fulltext_demo.c
Normal file
293
plugins/protocol_fulltext_demo.c
Normal file
|
@ -0,0 +1,293 @@
|
|||
/*
|
||||
* ws protocol handler plugin for "fulltext demo"
|
||||
*
|
||||
* Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* This file is made available under the Creative Commons CC0 1.0
|
||||
* Universal Public Domain Dedication.
|
||||
*
|
||||
* The person who associated a work with this deed has dedicated
|
||||
* the work to the public domain by waiving all of his or her rights
|
||||
* to the work worldwide under copyright law, including all related
|
||||
* and neighboring rights, to the extent allowed by law. You can copy,
|
||||
* modify, distribute and perform the work, even for commercial purposes,
|
||||
* all without asking permission.
|
||||
*
|
||||
* These test plugins are intended to be adapted for use in your code, which
|
||||
* may be proprietary. So unlike the library itself, they are licensed
|
||||
* Public Domain.
|
||||
*/
|
||||
|
||||
#if !defined (LWS_PLUGIN_STATIC)
|
||||
#define LWS_DLL
|
||||
#define LWS_INTERNAL
|
||||
#include <libwebsockets.h>
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#ifdef WIN32
|
||||
#include <io.h>
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
|
||||
struct vhd_fts_demo {
|
||||
const char *indexpath;
|
||||
};
|
||||
|
||||
struct pss_fts_demo {
|
||||
struct lwsac *result;
|
||||
struct lws_fts_result_autocomplete *ac;
|
||||
struct lws_fts_result_filepath *fp;
|
||||
|
||||
uint32_t *li;
|
||||
int done;
|
||||
|
||||
uint8_t first:1;
|
||||
uint8_t ac_done:1;
|
||||
|
||||
uint8_t fp_init_done:1;
|
||||
};
|
||||
|
||||
static int
|
||||
callback_fts(struct lws *wsi, enum lws_callback_reasons reason, void *user,
|
||||
void *in, size_t len)
|
||||
{
|
||||
struct vhd_fts_demo *vhd = (struct vhd_fts_demo *)
|
||||
lws_protocol_vh_priv_get(lws_get_vhost(wsi),
|
||||
lws_get_protocol(wsi));
|
||||
struct pss_fts_demo *pss = (struct pss_fts_demo *)user;
|
||||
uint8_t buf[LWS_PRE + 2048], *start = &buf[LWS_PRE], *p = start,
|
||||
*end = &buf[sizeof(buf) - LWS_PRE - 1];
|
||||
struct lws_fts_search_params params;
|
||||
const char *ccp = (const char *)in;
|
||||
struct lws_fts_result *result;
|
||||
struct lws_fts_file *jtf;
|
||||
int n;
|
||||
|
||||
switch (reason) {
|
||||
|
||||
case LWS_CALLBACK_PROTOCOL_INIT:
|
||||
vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
|
||||
lws_get_protocol(wsi),sizeof(struct vhd_fts_demo));
|
||||
if (!vhd)
|
||||
return 1;
|
||||
if (lws_pvo_get_str(in, "indexpath",
|
||||
(const char **)&vhd->indexpath))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
|
||||
case LWS_CALLBACK_HTTP:
|
||||
|
||||
pss->first = 1;
|
||||
pss->ac_done = 0;
|
||||
|
||||
/*
|
||||
* we have a "subdirectory" selecting the task
|
||||
*
|
||||
* /a/ = autocomplete
|
||||
* /r/ = results
|
||||
*/
|
||||
|
||||
if (strncmp(ccp, "/a/", 3) && strncmp(ccp, "/r/", 3))
|
||||
goto reply_404;
|
||||
|
||||
memset(¶ms, 0, sizeof(params));
|
||||
|
||||
params.needle = ccp + 3;
|
||||
if (*(ccp + 1) == 'a')
|
||||
params.flags = LWSFTS_F_QUERY_AUTOCOMPLETE;
|
||||
if (*(ccp + 1) == 'r')
|
||||
params.flags = LWSFTS_F_QUERY_FILES |
|
||||
LWSFTS_F_QUERY_FILE_LINES |
|
||||
LWSFTS_F_QUERY_QUOTE_LINE;
|
||||
params.max_autocomplete = 10;
|
||||
params.max_files = 10;
|
||||
|
||||
jtf = lws_fts_open(vhd->indexpath);
|
||||
if (!jtf) {
|
||||
lwsl_err("unable to open %s\n", vhd->indexpath);
|
||||
/* we'll inform the client in the JSON */
|
||||
goto reply_200;
|
||||
}
|
||||
|
||||
result = lws_fts_search(jtf, ¶ms);
|
||||
lws_fts_close(jtf);
|
||||
if (result) {
|
||||
pss->result = params.results_head;
|
||||
pss->ac = result->autocomplete_head;
|
||||
pss->fp = result->filepath_head;
|
||||
}
|
||||
/* NULL result will be told in the json as "indexed": 0 */
|
||||
|
||||
reply_200:
|
||||
if (lws_add_http_common_headers(wsi, HTTP_STATUS_OK,
|
||||
"text/html",
|
||||
LWS_ILLEGAL_HTTP_CONTENT_LEN, &p, end))
|
||||
return 1;
|
||||
|
||||
if (lws_finalize_write_http_header(wsi, start, &p, end))
|
||||
return 1;
|
||||
|
||||
lws_callback_on_writable(wsi);
|
||||
return 0;
|
||||
|
||||
reply_404:
|
||||
if (lws_add_http_common_headers(wsi, HTTP_STATUS_NOT_FOUND,
|
||||
"text/html",
|
||||
LWS_ILLEGAL_HTTP_CONTENT_LEN, &p, end))
|
||||
return 1;
|
||||
|
||||
if (lws_finalize_write_http_header(wsi, start, &p, end))
|
||||
return 1;
|
||||
return lws_http_transaction_completed(wsi);
|
||||
|
||||
case LWS_CALLBACK_CLOSED_HTTP:
|
||||
if (pss && pss->result)
|
||||
lwsac_free(&pss->result);
|
||||
break;
|
||||
|
||||
case LWS_CALLBACK_HTTP_WRITEABLE:
|
||||
|
||||
if (!pss)
|
||||
break;
|
||||
|
||||
n = LWS_WRITE_HTTP;
|
||||
if (pss->first)
|
||||
p += lws_snprintf((char *)p, lws_ptr_diff(end, p),
|
||||
"{\"indexed\": %d, \"ac\": [", !!pss->result);
|
||||
|
||||
while (pss->ac && lws_ptr_diff(end, p) > 256) {
|
||||
p += lws_snprintf((char *)p, lws_ptr_diff(end, p),
|
||||
"%c{\"ac\": \"%s\",\"matches\": %d,"
|
||||
"\"agg\": %d, \"elided\": %d}",
|
||||
pss->first ? ' ' : ',', (char *)(pss->ac + 1),
|
||||
pss->ac->instances, pss->ac->agg_instances,
|
||||
pss->ac->elided);
|
||||
|
||||
pss->first = 0;
|
||||
pss->ac = pss->ac->next;
|
||||
}
|
||||
|
||||
if (!pss->ac_done && !pss->ac && pss->fp) {
|
||||
pss->ac_done = 1;
|
||||
|
||||
p += lws_snprintf((char *)p, lws_ptr_diff(end, p),
|
||||
"], \"fp\": [");
|
||||
}
|
||||
|
||||
while (pss->fp && lws_ptr_diff(end, p) > 256) {
|
||||
if (!pss->fp_init_done) {
|
||||
p += lws_snprintf((char *)p,
|
||||
lws_ptr_diff(end, p),
|
||||
"%c{\"path\": \"%s\",\"matches\": %d,"
|
||||
"\"origlines\": %d,"
|
||||
"\"hits\": [", pss->first ? ' ' : ',',
|
||||
((char *)(pss->fp + 1)) +
|
||||
pss->fp->matches_length,
|
||||
pss->fp->matches,
|
||||
pss->fp->lines_in_file);
|
||||
|
||||
pss->li = ((uint32_t *)(pss->fp + 1));
|
||||
pss->done = 0;
|
||||
pss->fp_init_done = 1;
|
||||
pss->first = 0;
|
||||
} else {
|
||||
while (pss->done < pss->fp->matches &&
|
||||
lws_ptr_diff(end, p) > 256) {
|
||||
|
||||
p += lws_snprintf((char *)p,
|
||||
lws_ptr_diff(end, p),
|
||||
"%c\n{\"l\":%d,\"o\":%d,"
|
||||
"\"s\":\"%s\"}",
|
||||
!pss->done ? ' ' : ',',
|
||||
pss->li[0], pss->li[1],
|
||||
*((const char **)&pss->li[2]));
|
||||
pss->li += 2 + (sizeof(const char *) /
|
||||
sizeof(uint32_t));
|
||||
pss->done++;
|
||||
}
|
||||
|
||||
if (pss->done == pss->fp->matches) {
|
||||
*p++ = ']';
|
||||
pss->fp_init_done = 0;
|
||||
pss->fp = pss->fp->next;
|
||||
if (!pss->fp)
|
||||
*p++ = '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!pss->ac && !pss->fp) {
|
||||
n = LWS_WRITE_HTTP_FINAL;
|
||||
p += lws_snprintf((char *)p, lws_ptr_diff(end, p),
|
||||
"]}");
|
||||
}
|
||||
|
||||
if (lws_write(wsi, (uint8_t *)start,
|
||||
lws_ptr_diff(p, start), n) !=
|
||||
lws_ptr_diff(p, start))
|
||||
return 1;
|
||||
|
||||
if (n == LWS_WRITE_HTTP_FINAL) {
|
||||
if (pss->result)
|
||||
lwsac_free(&pss->result);
|
||||
if (lws_http_transaction_completed(wsi))
|
||||
return -1;
|
||||
} else
|
||||
lws_callback_on_writable(wsi);
|
||||
|
||||
return 0;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return lws_callback_http_dummy(wsi, reason, user, in, len);
|
||||
}
|
||||
|
||||
|
||||
#define LWS_PLUGIN_PROTOCOL_FULLTEXT_DEMO \
|
||||
{ \
|
||||
"lws-test-fts", \
|
||||
callback_fts, \
|
||||
sizeof(struct pss_fts_demo), \
|
||||
0, \
|
||||
0, NULL, 0 \
|
||||
}
|
||||
|
||||
#if !defined (LWS_PLUGIN_STATIC)
|
||||
|
||||
static const struct lws_protocols protocols[] = {
|
||||
LWS_PLUGIN_PROTOCOL_FULLTEXT_DEMO
|
||||
};
|
||||
|
||||
LWS_EXTERN LWS_VISIBLE int
|
||||
init_protocol_fulltext_demo(struct lws_context *context,
|
||||
struct lws_plugin_capability *c)
|
||||
{
|
||||
if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
|
||||
lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
|
||||
c->api_magic);
|
||||
return 1;
|
||||
}
|
||||
|
||||
c->protocols = protocols;
|
||||
c->count_protocols = LWS_ARRAY_SIZE(protocols);
|
||||
c->extensions = NULL;
|
||||
c->count_extensions = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
LWS_EXTERN LWS_VISIBLE int
|
||||
destroy_protocol_fulltext_demo(struct lws_context *context)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -208,7 +208,8 @@ ssh_ops_get_server_key(struct lws *wsi, uint8_t *buf, size_t len)
|
|||
lws_get_protocol(wsi));
|
||||
int n;
|
||||
|
||||
lseek(vhd->privileged_fd, 0, SEEK_SET);
|
||||
if (lseek(vhd->privileged_fd, 0, SEEK_SET) < 0)
|
||||
return 0;
|
||||
n = read(vhd->privileged_fd, buf, (int)len);
|
||||
if (n < 0) {
|
||||
lwsl_err("%s: read failed: %d\n", __func__, n);
|
||||
|
|
Loading…
Add table
Reference in a new issue