1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-09 00:00:04 +01:00

fulltext search

This commit is contained in:
Andy Green 2018-10-14 11:38:08 +08:00
parent 602b0934c8
commit d3bc2c3f4f
35 changed files with 35405 additions and 9 deletions

View file

@ -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" "")

View file

@ -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
View 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

View file

@ -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)

View file

@ -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;

View file

@ -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
View file

@ -0,0 +1,318 @@
# LWS Full Text Search
## Introduction
![lwsac flow](/doc-assets/lws-fts.svg)
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
View 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

File diff suppressed because it is too large Load diff

View file

@ -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;
}

View file

@ -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')

View 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()

View 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
```

View 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

View 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

File diff suppressed because it is too large Load diff

View 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(&params, 0, sizeof(params));
params.needle = argv[optind];
params.flags = flags;
params.max_autocomplete = 20;
params.max_files = 20;
result = lws_fts_search(jtf, &params);
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(&params.results_head);
optind++;
}
lws_fts_close(jtf);
return 0;
bail1:
close(ft);
bail:
lwsl_user("FAILED\n");
return 1;
}

View 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

View file

@ -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

View file

@ -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()

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -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 + "&nbsp;/&nbsp;" + 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);
}());

View file

@ -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 &

View 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(&params, 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, &params);
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

View file

@ -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);