路由处理 #

一、处理函数基础 #

1.1 基本处理函数 #

javascript
server.route({
    method: 'GET',
    path: '/hello',
    handler: (request, h) => {
        return 'Hello World!';
    }
});

1.2 处理函数参数 #

参数 说明
request 请求对象
h 响应工具包

1.3 返回值 #

处理函数可以返回:

  • 字符串
  • 对象(自动转为JSON)
  • Buffer
  • Stream
  • Promise
  • h.response() 创建的响应对象

二、请求对象 #

2.1 常用属性 #

javascript
server.route({
    method: 'GET',
    path: '/info',
    handler: (request, h) => {
        return {
            method: request.method,
            path: request.path,
            query: request.query,
            params: request.params,
            headers: request.headers,
            payload: request.payload,
            info: {
                remoteAddress: request.info.remoteAddress,
                host: request.info.host,
                received: request.info.received
            }
        };
    }
});

2.2 获取认证信息 #

javascript
server.route({
    method: 'GET',
    path: '/profile',
    options: {
        auth: 'jwt'
    },
    handler: (request, h) => {
        return {
            userId: request.auth.credentials.id,
            username: request.auth.credentials.username
        };
    }
});

2.3 获取应用状态 #

javascript
server.route({
    method: 'GET',
    path: '/app-info',
    handler: (request, h) => {
        return {
            app: request.server.app,
            plugins: request.server.plugins
        };
    }
});

三、响应工具包 #

3.1 h.response() #

创建响应对象:

javascript
server.route({
    method: 'GET',
    path: '/custom',
    handler: (request, h) => {
        return h.response({ message: 'Success' })
            .code(200)
            .header('X-Custom', 'value')
            .type('application/json');
    }
});

3.2 设置状态码 #

javascript
server.route({
    method: 'POST',
    path: '/create',
    handler: (request, h) => {
        return h.response({ id: 1 })
            .code(201)
            .message('Created');
    }
});

3.3 设置响应头 #

javascript
server.route({
    method: 'GET',
    path: '/headers',
    handler: (request, h) => {
        return h.response('Response with headers')
            .header('X-API-Version', '1.0')
            .header('Cache-Control', 'no-cache')
            .header('Content-Type', 'text/plain');
    }
});

3.4 设置Cookie #

javascript
server.route({
    method: 'POST',
    path: '/login',
    handler: (request, h) => {
        return h.response({ message: 'Logged in' })
            .state('sessionId', 'abc123', {
                ttl: 24 * 60 * 60 * 1000,
                isSecure: true,
                isHttpOnly: true
            });
    }
});

3.5 清除Cookie #

javascript
server.route({
    method: 'POST',
    path: '/logout',
    handler: (request, h) => {
        return h.response({ message: 'Logged out' })
            .unstate('sessionId');
    }
});

四、重定向 #

4.1 基本重定向 #

javascript
server.route({
    method: 'GET',
    path: '/redirect',
    handler: (request, h) => {
        return h.redirect('/target');
    }
});

4.2 永久重定向 #

javascript
server.route({
    method: 'GET',
    path: '/old-url',
    handler: (request, h) => {
        return h.redirect('/new-url').permanent(true);
    }
});

4.3 临时重定向 #

javascript
server.route({
    method: 'GET',
    path: '/temp-redirect',
    handler: (request, h) => {
        return h.redirect('/target').temporary(true);
    }
});

4.4 重写URL #

javascript
server.route({
    method: 'GET',
    path: '/rewrite',
    handler: (request, h) => {
        return h.redirect('/target').rewritable(false);
    }
});

五、响应类型 #

5.1 JSON响应 #

javascript
server.route({
    method: 'GET',
    path: '/json',
    handler: (request, h) => {
        return {
            message: 'Hello',
            data: { id: 1 }
        };
    }
});

5.2 文本响应 #

javascript
server.route({
    method: 'GET',
    path: '/text',
    handler: (request, h) => {
        return h.response('Plain text response')
            .type('text/plain')
            .charset('utf-8');
    }
});

5.3 HTML响应 #

javascript
server.route({
    method: 'GET',
    path: '/html',
    handler: (request, h) => {
        return h.response('<h1>Hello World</h1>')
            .type('text/html');
    }
});

5.4 文件响应 #

javascript
server.route({
    method: 'GET',
    path: '/download',
    handler: (request, h) => {
        return h.file('/path/to/file.pdf', {
            mode: 'attachment',
            filename: 'document.pdf'
        });
    }
});

5.5 流响应 #

javascript
const fs = require('fs');

server.route({
    method: 'GET',
    path: '/stream',
    handler: (request, h) => {
        const stream = fs.createReadStream('/path/to/file.txt');
        return h.response(stream)
            .type('text/plain')
            .header('Content-Disposition', 'attachment');
    }
});

六、异步处理 #

6.1 async/await #

javascript
server.route({
    method: 'GET',
    path: '/users/{id}',
    handler: async (request, h) => {
        const user = await User.findById(request.params.id);
        return user;
    }
});

6.2 Promise #

javascript
server.route({
    method: 'GET',
    path: '/data',
    handler: (request, h) => {
        return fetchData()
            .then(data => {
                return { data };
            })
            .catch(error => {
                throw Boom.badImplementation(error.message);
            });
    }
});

6.3 并行请求 #

javascript
server.route({
    method: 'GET',
    path: '/dashboard',
    handler: async (request, h) => {
        const [users, orders, stats] = await Promise.all([
            User.findAll(),
            Order.findAll(),
            Stats.getDaily()
        ]);
        
        return { users, orders, stats };
    }
});

七、错误处理 #

7.1 使用Boom #

javascript
const Boom = require('@hapi/boom');

server.route({
    method: 'GET',
    path: '/users/{id}',
    handler: async (request, h) => {
        const user = await User.findById(request.params.id);
        
        if (!user) {
            throw Boom.notFound('User not found');
        }
        
        return user;
    }
});

7.2 常用Boom错误 #

javascript
const Boom = require('@hapi/boom');

server.route({
    method: 'POST',
    path: '/action',
    handler: (request, h) => {
        const { action } = request.payload;
        
        switch (action) {
            case 'badRequest':
                throw Boom.badRequest('Invalid request');
            case 'unauthorized':
                throw Boom.unauthorized('Please login');
            case 'forbidden':
                throw Boom.forbidden('Access denied');
            case 'notFound':
                throw Boom.notFound('Resource not found');
            case 'conflict':
                throw Boom.conflict('Resource already exists');
            case 'badImplementation':
                throw Boom.badImplementation('Server error');
            default:
                return { message: 'OK' };
        }
    }
});

7.3 自定义错误数据 #

javascript
server.route({
    method: 'GET',
    path: '/validate',
    handler: (request, h) => {
        throw Boom.badRequest('Validation failed', {
            errors: [
                { field: 'email', message: 'Invalid email' },
                { field: 'password', message: 'Too short' }
            ]
        });
    }
});

7.4 全局错误处理 #

javascript
server.ext('onPreResponse', (request, h) => {
    const response = request.response;
    
    if (response.isBoom) {
        const error = response;
        const payload = {
            error: {
                statusCode: error.output.statusCode,
                error: error.output.payload.error,
                message: error.message
            }
        };
        
        if (error.data) {
            payload.error.details = error.data;
        }
        
        return h.response(payload).code(error.output.statusCode);
    }
    
    return h.continue;
});

八、响应验证 #

8.1 响应Schema验证 #

javascript
server.route({
    method: 'GET',
    path: '/users/{id}',
    options: {
        response: {
            schema: Joi.object({
                id: Joi.number().required(),
                name: Joi.string().required(),
                email: Joi.string().email().required()
            })
        }
    },
    handler: async (request, h) => {
        return await User.findById(request.params.id);
    }
});

8.2 响应修改 #

javascript
server.route({
    method: 'GET',
    path: '/users',
    options: {
        response: {
            modify: true,
            options: {
                stripUnknown: true
            }
        }
    },
    handler: async (request, h) => {
        return await User.findAll();
    }
});

8.3 响应失败处理 #

javascript
server.route({
    method: 'GET',
    path: '/data',
    options: {
        response: {
            schema: Joi.object({
                id: Joi.number(),
                name: Joi.string()
            }),
            failAction: (request, h, err) => {
                console.error('Response validation failed:', err);
                throw err;
            }
        }
    },
    handler: (request, h) => {
        return { id: 1, name: 'Test', extra: 'field' };
    }
});

九、处理函数组织 #

9.1 分离处理函数 #

handlers/users.js:

javascript
const userService = require('../services/userService');
const Boom = require('@hapi/boom');

const getAll = async (request, h) => {
    const { page, limit } = request.query;
    return await userService.findAll({ page, limit });
};

const getById = async (request, h) => {
    const user = await userService.findById(request.params.id);
    if (!user) {
        throw Boom.notFound('User not found');
    }
    return user;
};

const create = async (request, h) => {
    const user = await userService.create(request.payload);
    return h.response(user).code(201);
};

const update = async (request, h) => {
    const user = await userService.update(request.params.id, request.payload);
    return user;
};

const remove = async (request, h) => {
    await userService.remove(request.params.id);
    return h.response().code(204);
};

module.exports = {
    getAll,
    getById,
    create,
    update,
    remove
};

9.2 使用处理函数 #

routes/users.js:

javascript
const handlers = require('../handlers/users');

module.exports = [
    {
        method: 'GET',
        path: '/users',
        handler: handlers.getAll
    },
    {
        method: 'GET',
        path: '/users/{id}',
        handler: handlers.getById
    },
    {
        method: 'POST',
        path: '/users',
        handler: handlers.create
    },
    {
        method: 'PUT',
        path: '/users/{id}',
        handler: handlers.update
    },
    {
        method: 'DELETE',
        path: '/users/{id}',
        handler: handlers.remove
    }
];

十、完整示例 #

10.1 RESTful API处理 #

javascript
const Hapi = require('@hapi/hapi');
const Joi = require('joi');
const Boom = require('@hapi/boom');

const users = [];

const init = async () => {
    const server = Hapi.server({ port: 3000 });

    server.route([
        {
            method: 'GET',
            path: '/users',
            options: {
                validate: {
                    query: Joi.object({
                        page: Joi.number().default(1),
                        limit: Joi.number().default(10)
                    })
                }
            },
            handler: (request, h) => {
                const { page, limit } = request.query;
                const start = (page - 1) * limit;
                const end = start + limit;
                
                return {
                    users: users.slice(start, end),
                    total: users.length,
                    page,
                    limit
                };
            }
        },
        {
            method: 'GET',
            path: '/users/{id}',
            options: {
                validate: {
                    params: Joi.object({
                        id: Joi.number().required()
                    })
                }
            },
            handler: (request, h) => {
                const user = users.find(u => u.id === parseInt(request.params.id));
                if (!user) {
                    throw Boom.notFound('User not found');
                }
                return user;
            }
        },
        {
            method: 'POST',
            path: '/users',
            options: {
                validate: {
                    payload: Joi.object({
                        name: Joi.string().required(),
                        email: Joi.string().email().required()
                    })
                }
            },
            handler: (request, h) => {
                const user = {
                    id: users.length + 1,
                    ...request.payload,
                    createdAt: new Date()
                };
                users.push(user);
                return h.response(user).code(201);
            }
        },
        {
            method: 'PUT',
            path: '/users/{id}',
            options: {
                validate: {
                    params: Joi.object({
                        id: Joi.number().required()
                    }),
                    payload: Joi.object({
                        name: Joi.string(),
                        email: Joi.string().email()
                    }).min(1)
                }
            },
            handler: (request, h) => {
                const index = users.findIndex(u => u.id === parseInt(request.params.id));
                if (index === -1) {
                    throw Boom.notFound('User not found');
                }
                users[index] = { ...users[index], ...request.payload };
                return users[index];
            }
        },
        {
            method: 'DELETE',
            path: '/users/{id}',
            options: {
                validate: {
                    params: Joi.object({
                        id: Joi.number().required()
                    })
                }
            },
            handler: (request, h) => {
                const index = users.findIndex(u => u.id === parseInt(request.params.id));
                if (index === -1) {
                    throw Boom.notFound('User not found');
                }
                users.splice(index, 1);
                return h.response().code(204);
            }
        }
    ]);

    await server.start();
    console.log('Server running on %s', server.info.uri);
};

init();

十一、总结 #

路由处理要点:

概念 说明
handler 处理函数
request 请求对象
h 响应工具包
h.response() 创建响应
Boom 错误处理
验证 输入输出验证

下一步,让我们深入学习Hapi的插件系统!

最后更新:2026-03-28