The Selector-Handler plugin enables CSS selector-based HTML manipulation through HTTP Range headers. This unique feature allows clients to request specific HTML elements from documents, enabling fine-grained content extraction and modification.
Rusty Beam intentionally repurposes the HTTP Range header to support CSS selectors, providing a powerful API for HTML manipulation. The selector-handler plugin processes these special Range headers and returns only the selected HTML elements.
Range: selector={css-selector}
formatThe selector-handler plugin requires minimal configuration:
<li itemprop="plugin" itemscope itemtype="https://rustybeam.net/schema/Plugin">
<span itemprop="library">file://./plugins/selector-handler.so</span>
</li>
The selector-handler plugin currently has no configuration parameters. It processes all requests with Range: selector=
headers.
The plugin recognizes Range headers in the following format:
Range: selector={css-selector}
Range: selector=h1
- Select all h1 elementsRange: selector=.content
- Select elements with class "content"Range: selector=#header
- Select element with id "header"Range: selector=article p
- Select all p elements within article elementsRange: selector=input[type="text"]
- Select text input elementsComplex selectors should be URL-encoded in the Range header:
# Selector: div > p.highlight
Range: selector=div%20%3E%20p.highlight
# Selector: a[href^="https://"]
Range: selector=a%5Bhref%5E%3D%22https%3A%2F%2F%22%5D
Retrieve specific elements from an HTML document:
GET /page.html HTTP/1.1
Host: localhost:3000
Range: selector=h1,h2
# Response: HTML fragment containing all h1 and h2 elements
Replace selected elements with new content:
PUT /page.html HTTP/1.1
Host: localhost:3000
Range: selector=.announcement
Content-Type: text/html
<div class="announcement">Updated announcement content</div>
Append content to selected elements:
POST /guestbook.html HTTP/1.1
Host: localhost:3000
Range: selector=ul.entries
Content-Type: text/html
<li>New guestbook entry</li>
Remove selected elements from the document:
DELETE /page.html HTTP/1.1
Host: localhost:3000
Range: selector=.deprecated-content
When elements are found, the plugin returns HTTP 206:
HTTP/1.1 206 Partial Content
Content-Type: text/html
<h1>Page Title</h1>
<h1>Section Title</h1>
When no elements match the selector:
HTTP/1.1 416 Range Not Satisfiable
Content-Type: text/plain
No elements match selector: .nonexistent
When the CSS selector is malformed:
HTTP/1.1 400 Bad Request
Content-Type: text/plain
Invalid CSS selector: div >> p
The plugin supports standard CSS3 selectors:
Selector | Description | Example |
---|---|---|
element |
Type selector | p , div , span |
.class |
Class selector | .highlight , .nav-item |
#id |
ID selector | #header , #main-content |
* |
Universal selector | * , div * |
Combinator | Description | Example |
---|---|---|
A B |
Descendant | article p |
A > B |
Child | ul > li |
A + B |
Adjacent sibling | h1 + p |
A ~ B |
General sibling | h1 ~ p |
Selector | Description | Example |
---|---|---|
[attr] |
Has attribute | [disabled] |
[attr=value] |
Exact match | [type="text"] |
[attr^=value] |
Starts with | [href^="https://"] |
[attr$=value] |
Ends with | [src$=".png"] |
[attr*=value] |
Contains | [class*="nav"] |
Common pseudo-classes supported:
:first-child
, :last-child
, :nth-child(n)
:first-of-type
, :last-of-type
, :nth-of-type(n)
:not(selector)
:empty
, :has(selector)
Typical pipeline order:
1. basic-auth.so → Authenticates user
2. authorization.so → Checks selector permissions
3. selector-handler.so → Processes selector requests
4. file-handler.so → Serves complete files
5. access-log.so → Logs requests
The selector-handler plugin works with the authorization plugin to enable element-level access control:
<!-- Authorization rule example -->
<tr itemscope itemtype="https://rustybeam.net/schema/AuthorizationRule">
<td itemprop="username">*</td>
<td itemprop="path">/page.html</td>
<td itemprop="selector">.public</td>
<td itemprop="method">GET</td>
<td itemprop="action">allow</td>
</tr>
This allows anonymous users to access only elements with class "public" from page.html.
Extract specific content from web pages:
# Get all article headlines
curl -H "Range: selector=article h2" http://localhost:3000/blog.html
# Extract navigation menu
curl -H "Range: selector=nav ul" http://localhost:3000/index.html
Update specific page sections without modifying the entire document:
# Update news section
curl -X PUT -H "Range: selector=#news" \
-H "Content-Type: text/html" \
-d '<div id="news">Latest news here</div>' \
http://localhost:3000/index.html
Append entries to lists:
# Add comment to comment list
curl -X POST -H "Range: selector=ul#comments" \
-H "Content-Type: text/html" \
-d '<li>New comment</li>' \
http://localhost:3000/comments.html
Implement access control at the element level:
Error | HTTP Status | Description |
---|---|---|
Invalid selector syntax | 400 Bad Request | CSS selector is malformed |
No matching elements | 416 Range Not Satisfiable | Selector matches no elements |
File not found | 404 Not Found | Requested file doesn't exist |
Not an HTML file | Pass through | Non-HTML files passed to next plugin |
Authorization denied | 403 Forbidden | User lacks permission for selector |
# Get all paragraphs
curl -H "Range: selector=p" http://localhost:3000/article.html
# Get element by ID
curl -H "Range: selector=#main-content" http://localhost:3000/page.html
# Update footer
curl -X PUT -H "Range: selector=footer" \
-H "Content-Type: text/html" \
-d '<footer>© 2024 Updated</footer>' \
http://localhost:3000/page.html
# Delete advertisements
curl -X DELETE -H "Range: selector=.advertisement" \
http://localhost:3000/page.html
// Fetch specific elements
fetch('http://localhost:3000/page.html', {
headers: {
'Range': 'selector=article h2'
}
})
.then(response => response.text())
.then(html => {
console.log('Headlines:', html);
});
// Update element
fetch('http://localhost:3000/page.html', {
method: 'PUT',
headers: {
'Range': 'selector=#status',
'Content-Type': 'text/html'
},
body: '<div id="status">Online</div>'
});
import requests
# Get navigation menu
response = requests.get(
'http://localhost:3000/index.html',
headers={'Range': 'selector=nav'}
)
print(response.text)
# Append to list
response = requests.post(
'http://localhost:3000/tasks.html',
headers={
'Range': 'selector=ul#task-list',
'Content-Type': 'text/html'
},
data='<li>New task</li>'
)
Enable verbose logging to see selector processing:
./rusty-beam -v config.html
Issue | Cause | Solution |
---|---|---|
Always returns full document | Plugin not loaded or wrong order | Check plugin configuration and order |
416 for valid selectors | Selector matches no elements | Verify selector and HTML structure |
403 Forbidden | Authorization rules blocking | Check authorization configuration |
Encoding issues | Special characters in selector | URL-encode the selector value |