Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

help request: How to create a custom-404-redirect plugin? #12023

Open
rohitkrishna-marneni opened this issue Mar 6, 2025 · 3 comments
Open
Labels
plugin question label for questions asked by users

Comments

@rohitkrishna-marneni
Copy link

rohitkrishna-marneni commented Mar 6, 2025

Description

I want to create a plugin where if upstream status is 404, I will be able to specify a custom 404 page and it should go there.

So this is what I have so far.

My apisix configuration for route

services:
  - name: my-service
    upstream:
      pass_host: pass
      nodes:
        - host: my-api.com
          port: 80
          weight: 1
          priority: 0
      type: roundrobin
      scheme: http
      name: my-api
      hash_on: vars
      timeout:
        send: 10
        read: 60
        connect: 2
    routes:
      - name: my product page route
        hosts:
          - "mysite.com"
        uris:
          - /shop/products/*
        priority: 0
        plugins:
          custom-404-redirect:
            redirect_url: /not-found.html

And my plugin code is as shown below

local core = require("apisix.core")
local ngx = ngx

local plugin_name = "custom-404-redirect"

local schema = {
    type = "object",
    properties = {
        redirect_url = {type = "string", minLength = 1}
    },
    required = {"redirect_url"}
}

local _M = {
    version = 0.1,
    priority = 1010,  -- set the plugin priority, higher means earlier execution
    name = plugin_name,
    schema = schema
}

function _M.check_schema(conf)
    return core.schema.check(schema, conf)
end

function _M.header_filter(conf, ctx)
    if ngx.status == 404 then
        ngx.status = 302
        ngx.header["Location"] = conf.redirect_url
        ngx.log(ngx.ERR, "Redirecting to custom 404 page: ", conf.redirect_url)
        ngx.exit(ngx.HTTP_MOVED_TEMPORARILY)
        end
end

return _M

But this is not working and simply throwing a 500 status code instead of going to a 404 as expected. Is it not possible to do it via plugin?

Note: I found below links but they are still open or dont work

Note: I also opened another issue asking if apisix has a built-in plugin but someone commented saying that we can use custom plugin to do so: #12007. Hence I am creating this issue since I am stuck at creating a custom plugin for this usecase.

Environment

  • APISIX version (run apisix version): 3.10.0-debian
  • Operating system (run uname -a): Ubuntu
@github-project-automation github-project-automation bot moved this to 📋 Backlog in Apache APISIX backlog Mar 6, 2025
@dosubot dosubot bot added plugin question label for questions asked by users labels Mar 6, 2025
@juzhiyuan

This comment has been minimized.

@github-project-automation github-project-automation bot moved this from 📋 Backlog to ✅ Done in Apache APISIX backlog Mar 7, 2025
@juzhiyuan juzhiyuan reopened this Mar 7, 2025
@github-project-automation github-project-automation bot moved this from ✅ Done to 📋 Backlog in Apache APISIX backlog Mar 7, 2025
@mikyll
Copy link
Contributor

mikyll commented Mar 17, 2025

@rohitkrishna-marneni

I slightly changed your plugin, by removing ngx.exit(ngx.HTTP_MOVED_TEMPORARILY).

I tested it with the example below, using external service httpbin.org, and it seems to be working. Can you have a look and provide a feedback?

Example

File Structure

.
├── conf/
│   ├── apisix.yaml
│   └── config.yaml
├── plugins/
│   └── custom-404-redirect.lua
└── compose.yaml

File Sources

File conf/config.yaml:

apisix:
  extra_lua_path: "/usr/local/apisix/apisix/plugins/custom/?.lua"

deployment:
  role: data_plane
  role_data_plane:
    config_provider: yaml

plugins:
  - custom-404-redirect

#END

File conf/apisix.yaml:

upstreams:
  - id: external_httpbin
    nodes:
      "httpbin.org": 1
    scheme: https

routes:
  - id: 1
    uri: /anything
    upstream_id: external_httpbin
  - id: 2
    uris: 
      - /shop/products/*
    upstream_id: external_httpbin
    plugins:
      custom-404-redirect:
        redirect_url: /anything

#END

File plugins/custom-404-redirect.lua:

local core = require("apisix.core")
local ngx = ngx

local plugin_name = "custom-404-redirect"

local schema = {
  type = "object",
  properties = {
    redirect_url = { type = "string", minLength = 1 }
  },
  required = { "redirect_url" }
}

local _M = {
  version = 0.1,
  priority = 1010, -- set the plugin priority, higher means earlier execution
  name = plugin_name,
  schema = schema
}

function _M.check_schema(conf)
  return core.schema.check(schema, conf)
end

function _M.header_filter(conf, ctx)
  if ngx.status == 404 then
    ngx.status = ngx.HTTP_MOVED_TEMPORARILY
    ngx.header["Location"] = conf.redirect_url
    core.log.warn("Redirecting to custom 404 page: " .. conf.redirect_url)
    -- ngx.exit(ngx.HTTP_MOVED_TEMPORARILY) -- Comment/remove this line
  end
end

File compose.yaml:

services:
  apisix:
    container_name: apisix-test-redirect
    image: apache/apisix:latest
    restart: no
    stdin_open: true
    tty: true
    environment:
      - APISIX_STAND_ALONE=true
    volumes:
      - ./conf/config.yaml:/usr/local/apisix/conf/config.yaml
      - ./conf/apisix.yaml:/usr/local/apisix/conf/apisix.yaml
      - ./plugins:/usr/local/apisix/apisix/plugins/custom/apisix/plugins
    ports:
      - "9080:9080/tcp"
      - "9443:9443/tcp"

Testing

Setup

Run the following:

docker compose up

Testing the Route

Run the following:

curl -sL localhost:9080/shop/products/test -i

Result:

HTTP/1.1 302 Moved Temporarily
Content-Type: text/html
Content-Length: 233
Connection: keep-alive
Date: Mon, 17 Mar 2025 16:58:54 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Server: APISIX/3.11.0
Location: /anything

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 388
Connection: keep-alive
Date: Mon, 17 Mar 2025 16:58:55 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Server: APISIX/3.11.0

{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Host": "localhost", 
    "User-Agent": "curl/8.5.0", 
    "X-Amzn-Trace-Id": "Root=1-67d854ce-0853fc4a1447023a613f0439", 
    "X-Forwarded-Host": "localhost"
  }, 
  "json": null, 
  "method": "GET", 
  "origin": "172.X.X.X, 89.X.X.X", 
  "url": "https://localhost/anything"
}

@rohitkrishna-marneni
Copy link
Author

@mikyll Thanks for the help. Now it seems to be working, however I have a weird issue.

I applied this to a route called "/shop/*". So when I go to a page like "/shop/invalid-product" I get proper 404 page as expected.

However if I go to a valid page "/shop/valid-product", then this page makes some asset calls(css, js, ajax etc). Lets assume one such call is "/shop/price/...". Lets also assume that this endpoint throws a 404, but because we have a custom plugin its returning a 404 but with custom html body(the 404 url that we gave) and as a result that 404 page is being embedded into my main "/shop/valid-product" page.

Is there a way to apply this to only the main endpoint which I am hitting from the browser? That is if I go "google.com", this should apply only to that endpoint and not for any ajax/css/js(etc) calls that happen on that page?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
plugin question label for questions asked by users
Projects
Status: 📋 Backlog
Development

No branches or pull requests

3 participants