Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions _examples/bot-detection/fastly.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# This file describes a Fastly Compute package. To learn more visit:
# https://developer.fastly.com/reference/fastly-toml/

authors = ["oss@fastly.com"]
description = ""
language = "go"
manifest_version = 2
name = "bot-detection"
25 changes: 25 additions & 0 deletions _examples/bot-detection/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2022 Fastly, Inc.

package main

import (
"context"
"fmt"
"log"

"github.com/fastly/compute-sdk-go/fsthttp"
)

func main() {
fsthttp.ServeFunc(func(ctx context.Context, w fsthttp.ResponseWriter, r *fsthttp.Request) {
bot, _ := r.BotDetection()

if bot.Analyzed {
if bot.Detected {
log.Println(w, "request from bot:", bot.Category, bot.Name)
}
}

fmt.Fprintf(w, "Hello, %s!\n", r.RemoteAddr)
})
}
117 changes: 117 additions & 0 deletions fsthttp/bot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package fsthttp

import "github.com/fastly/compute-sdk-go/internal/abi/fastly"

type BotCategory = fastly.BotCategory

const (
// BotCategoryNone indicates bot detection was not executed, or no bot was detected.
BotCategoryNone = fastly.BotCategoryNone

// BotCategorySuspected is for a suspected bot.
BotCategorySuspected = fastly.BotCategorySuspected

// BotCategoryAccessibility is for tools that make content accessible (e.g., screen readers).
BotCategoryAccessibility = fastly.BotCategoryAccessibility

// BotCategoryAICrawler is for crawlers used for training AIs and LLMs, generally used for building AI models or indexes.
BotCategoryAICrawler = fastly.BotCategoryAICrawler

// BotCategoryAIFetcher is for fetchers used by AIs and LLMs for enriching results in response to a user query.
BotCategoryAIFetcher = fastly.BotCategoryAIFetcher

// BotCategoryContentFetcher is for tools that extract content from websites to be used elsewhere.
BotCategoryContentFetcher = fastly.BotCategoryContentFetcher

// BotCategoryMonitoringSiteTools is for tools that access your website to monitor things like performance, uptime, and proving domain control.
BotCategoryMonitoringSiteTools = fastly.BotCategoryMonitoringSiteTools

// BotCategoryOnlineMarketing is for crawlers from online marketing platforms (e.g., Facebook, Pinterest).
BotCategoryOnlineMarketing = fastly.BotCategoryOnlineMarketing

// BotCategoryPagePreview is for tools that access your website to show a preview of the page in other online services and social media platforms.
BotCategoryPagePreview = fastly.BotCategoryPagePreview

// BotCategoryPlatformIntegrations is for integration with other platforms by accessing the website's API, notably Webhooks.
BotCategoryPlatformIntegrations = fastly.BotCategoryPlatformIntegrations

// BotCategoryResearch is for commercial and academic tools that collect and analyze data for research purposes.
BotCategoryResearch = fastly.BotCategoryResearch

// BotCategorySearchEngineCrawler is for crawlers that index your website for search engines.
BotCategorySearchEngineCrawler = fastly.BotCategorySearchEngineCrawler

// BotCategorySearchEngineSpecialization is for tools that support search engine optimization tasks (e.g., link analysis, ranking).
BotCategorySearchEngineSpecialization = fastly.BotCategorySearchEngineSpecialization

// BotCategorySecurityTools is for security analysis tools that inspect your website for vulnerabilities, misconfigurations and other security features.
BotCategorySecurityTools = fastly.BotCategorySecurityTools

// BotCategoryUnknown indicates the detected bot belongs to a category not recognized by this SDK version.
BotCategoryUnknown = fastly.BotCategoryUnknown
)

type BotDetectionResult struct {
// Analyzed indicates if the request was analyzed by the bot detection framework.
Analyzed bool

// Detected indicates if a bot was detected.
Detected bool

// Name is string identifying the specific bot detected (e.g., `GoogleBot`, `GPTBot`, `Bingbot`).
// Returns the empty string if bot detection was not executed or no bot was detected.
//
// Note: String values may change over time. Use this for logging or informational purposes.
// For conditional logic, use CategoryKind.
Name string

// Category is a string indicating the type of bot detected (e.g., `SEARCH-ENGINE-CRAWLER`, `AI-CRAWLER`,
// `SUSPECTED-BOT`).
//
// Note: String values may change over time. Use this for logging or informational purposes.
// For conditional logic, use [`get_bot_category_kind()`][Self::get_bot_category_kind].
Category string

// An enum uniquely identifying the type of bot detected.
CategoryKind BotCategory

// Verified is whether the detected bot is a verified bot.
Verfied bool
}

func (r *Request) BotDetection() (*BotDetectionResult, error) {
var result BotDetectionResult

var err error
if result.Analyzed, err = r.downstream.req.DownstreamBotAnalyzed(); err != nil {
return nil, err
}

// Didn't analyze the request? Nothing else to do.
if !result.Analyzed {
return &result, nil
}

if result.Detected, err = r.downstream.req.DownstreamBotDetected(); err != nil {
return nil, err
}

// Request wasn't detected as a bot? Nothing to fill in.
if !result.Detected {
return &result, nil
}

if result.Name, err = r.downstream.req.DownstreamBotName(); err != nil {
return nil, err
}

if result.Category, err = r.downstream.req.DownstreamBotCategory(); err != nil {
return nil, err
}

if result.Verfied, err = r.downstream.req.DownstreamBotVerified(); err != nil {
return nil, err
}

return &result, nil
}
24 changes: 24 additions & 0 deletions internal/abi/fastly/hostcalls_noguest.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,30 @@ func (r *HTTPRequest) DownstreamFastlyKeyIsValid() (bool, error) {
return false, fmt.Errorf("not implemented")
}

func (r *HTTPRequest) DownstreamBotAnalyzed() (bool, error) {
return false, fmt.Errorf("not implemented")
}

func (r *HTTPRequest) DownstreamBotDetected() (bool, error) {
return false, fmt.Errorf("not implemented")
}

func (r *HTTPRequest) DownstreamBotName() (string, error) {
return "", fmt.Errorf("not implemented")
}

func (r *HTTPRequest) DownstreamBotCategory() (string, error) {
return "", fmt.Errorf("not implemented")
}

func (r *HTTPRequest) DownstreamBotCategoryKind() (uint32, error) {
return 0, fmt.Errorf("not implemented")
}

func (r *HTTPRequest) DownstreamBotVerified() (bool, error) {
return false, fmt.Errorf("not implemented")
}

func NewHTTPRequest() (*HTTPRequest, error) {
return nil, fmt.Errorf("not implemented")
}
Expand Down
184 changes: 184 additions & 0 deletions internal/abi/fastly/http_guest.go
Original file line number Diff line number Diff line change
Expand Up @@ -1832,6 +1832,190 @@ func (r *HTTPRequest) DownstreamFastlyKeyIsValid() (bool, error) {
return valid.b, nil
}

// witx:
//
// (@interface func (export "downstream_bot_analyzed")
// (param $req $request_handle)
// (result $err (expected $bot_analyzed (error $fastly_status)))
// )
//
//go:wasmimport fastly_http_downstream downstream_bot_analyzed
//go:noescape
func fastlyHTTPDownstreamBotAnalyzed(
req requestHandle,
analyzed prim.Pointer[bool],
) FastlyStatus

func (r *HTTPRequest) DownstreamBotAnalyzed() (bool, error) {
var analyzed struct {
b bool
_ prim.Usize // align padding
}
if err := fastlyHTTPDownstreamBotAnalyzed(
r.h,
prim.ToPointer(&analyzed.b),
).toError(); err != nil {
return false, err
}

return analyzed.b, nil
}

// witx:
//
// (@interface func (export "downstream_bot_detected")
// (param $req $request_handle)
// (result $err (expected $bot_detected (error $fastly_status)))
// )
//
//go:wasmimport fastly_http_downstream downstream_bot_detected
//go:noescape
func fastlyHTTPDownstreamBotDetected(
req requestHandle,
analyzed prim.Pointer[bool],
) FastlyStatus

func (r *HTTPRequest) DownstreamBotDetected() (bool, error) {
var detected struct {
b bool
_ prim.Usize // align padding
}
if err := fastlyHTTPDownstreamBotDetected(
r.h,
prim.ToPointer(&detected.b),
).toError(); err != nil {
return false, err
}

return detected.b, nil
}

// witx:
//
// (@interface func (export "downstream_bot_name")
// (param $req $request_handle)
// (param $bot_name_out (@witx pointer (@witx char8)))
// (param $bot_name_max_len (@witx usize))
// (param $nwritten_out (@witx pointer (@witx usize)))
// (result $err (expected (error $fastly_status)))
// )
//
//go:wasmimport fastly_http_downstream downstream_bot_name
//go:noescape
func fastlyHTTPReqDownstreamBotName(
req requestHandle,
botNameOut prim.Pointer[prim.Char8],
botNameMaxLen prim.Usize,
nwrittenOut prim.Pointer[prim.Usize],
) FastlyStatus

// DownstreamBotName returns the bot name detected
func (r *HTTPRequest) DownstreamBotName() (string, error) {
value, err := withAdaptiveBuffer(DefaultSmallBufLen, func(buf *prim.WriteBuffer) FastlyStatus {
return fastlyHTTPReqDownstreamBotName(
r.h,
prim.ToPointer(buf.Char8Pointer()),
buf.Cap(),
prim.ToPointer(buf.NPointer()),
)
})
if err != nil {
return "", err
}
return value.ToString(), nil
}

// witx:
//
// (@interface func (export "downstream_bot_category")
// (param $req $request_handle)
// (param $bot_category_out (@witx pointer (@witx char8)))
// (param $bot_category_max_len (@witx usize))
// (param $nwritten_out (@witx pointer (@witx usize)))
// (result $err (expected (error $fastly_status)))
// )
//
//go:wasmimport fastly_http_downstream downstream_bot_category
//go:noescape
func fastlyHTTPReqDownstreamBotCategory(
req requestHandle,
botCategoryOut prim.Pointer[prim.Char8],
botCategoryMaxLen prim.Usize,
nwrittenOut prim.Pointer[prim.Usize],
) FastlyStatus

// DownstreamBotCategory returns the bot category
func (r *HTTPRequest) DownstreamBotCategory() (string, error) {
value, err := withAdaptiveBuffer(DefaultSmallBufLen, func(buf *prim.WriteBuffer) FastlyStatus {
return fastlyHTTPReqDownstreamBotCategory(
r.h,
prim.ToPointer(buf.Char8Pointer()),
buf.Cap(),
prim.ToPointer(buf.NPointer()),
)
})
if err != nil {
return "", err
}
return value.ToString(), nil
}

// witx:
//
// (@interface func (export "downstream_bot_category_kind")
// (param $req $request_handle)
// (result $err (expected $bot_category_kind (error $fastly_status)))
// )
//
//go:wasmimport fastly_http_downstream downstream_bot_category_kind
//go:noescape
func fastlyHTTPDownstreamBotCategoryKind(
req requestHandle,
kind prim.Pointer[prim.U32],
) FastlyStatus

func (r *HTTPRequest) DownstreamBotCategoryKind() (uint32, error) {
var kind prim.U32
if err := fastlyHTTPDownstreamBotCategoryKind(
r.h,
prim.ToPointer(&kind),
).toError(); err != nil {
return 0, err
}

return uint32(kind), nil
}

// witx:
//
// (@interface func (export "downstream_bot_verified")
//
// (param $req $request_handle)
// (result $err (expected $bot_verified (error $fastly_status)))
// )
//
//go:wasmimport fastly_http_downstream downstream_bot_verified
//go:noescape
func fastlyHTTPDownstreamBotVerified(
req requestHandle,
analyzed prim.Pointer[bool],
) FastlyStatus

func (r *HTTPRequest) DownstreamBotVerified() (bool, error) {
var verified struct {
b bool
_ prim.Usize // align padding
}
if err := fastlyHTTPDownstreamBotVerified(
r.h,
prim.ToPointer(&verified.b),
).toError(); err != nil {
return false, err
}

return verified.b, nil
}

// witx:
//
// ;;; Hostcall for Fastly Compute guests to inspect request HTTP traffic
Expand Down
Loading
Loading