Un error HTTP 500 en un endpoint OAI-PMH puede parecer, a primera vista, un problema de red, proxy, Cloudflare, certificados SSL o incluso de la interfaz Angular. Sin embargo, en este caso la causa estaba en un lugar mucho más específico: los permisos del directorio de caché utilizado por el servicio OAI de DSpace.
El repositorio institucional comenzó a presentar problemas de cosecha desde ANID. Al consultar el endpoint OAI-PMH, todas las solicitudes devolvían un error HTTP 500:
https://repositorio.infodi.cl/server/oai/request?verb=ListIdentifiers&metadataPrefix=oai_dc
La respuesta entregada por el backend era:
{
"timestamp": "2026-07-28T20:57:32.968+00:00",
"status": 500,
"error": "Internal Server Error",
"message": "An internal read or write operation failed",
"path": "/server/oai/request"
}
El mensaje era genérico y no indicaba directamente qué archivo, componente o servicio estaba fallando.
No. Esta fue una de las primeras conclusiones importantes.
En DSpace 7, Angular corresponde a la interfaz pública del repositorio. El servicio OAI-PMH, en cambio, es procesado por el backend Java de DSpace, desplegado en Tomcat.
El endpoint afectado era:
/server/oai/request
Por lo tanto, el error no estaba relacionado con componentes, rutas, plantillas o servicios Angular.
La consulta pública pasaba por Nginx y por la capa de seguridad del dominio. Para descartar esos componentes, realizamos una consulta directamente contra Tomcat:
curl -v \
"http://127.0.0.1:8080/server/oai/request?verb=Identify"
El resultado fue nuevamente:
HTTP/1.1 500
{
"status": 500,
"error": "Internal Server Error",
"message": "An internal read or write operation failed"
}
Esta prueba permitió descartar como causa principal:
El problema estaba dentro de DSpace o de alguno de sus componentes internos.
Probamos los principales verbos del protocolo OAI-PMH:
curl -sk \
"https://repositorio.infodi.cl/server/oai/request?verb=Identify"
curl -sk \
"https://repositorio.infodi.cl/server/oai/request?verb=ListMetadataFormats"
curl -sk \
"https://repositorio.infodi.cl/server/oai/request?verb=ListSets"
curl -sk \
"https://repositorio.infodi.cl/server/oai/request?verb=ListIdentifiers&metadataPrefix=oai_dc"
Todos devolvían HTTP 500.
Este comportamiento era relevante porque permitía descartar, en principio, que el problema estuviera limitado a:
Si incluso Identify fallaba, el servicio OAI completo estaba teniendo problemas al inicializar o generar su respuesta.
Al revisar los logs de Tomcat apareció repetidamente la siguiente excepción:
java.lang.IllegalStateException:
getOutputStream() has already been called for this response
Este mensaje podía inducir a pensar que el problema estaba en el manejo del flujo de salida HTTP, pero en realidad era un error secundario.
El flujo probable era el siguiente:
getOutputStream().getWriter().Por tanto, esta excepción no era la causa raíz, sino el resultado del intento de representar el error original.
Debido al mensaje:
An internal read or write operation failed
una de las hipótesis iniciales fue falta de espacio, inodos agotados o un sistema de archivos montado en modo de solo lectura.
Se revisó el almacenamiento:
df -h /
df -h /app
df -h /tmp
df -i /
Los resultados indicaron:
/ 50 % utilizado
/app 82 % utilizado, con 356 GB disponibles
/tmp 50 % utilizado
inodos 9 % utilizados
Por lo tanto, no existía falta de espacio ni agotamiento de inodos.
Durante la revisión también detectamos un volumen importante de logs:
/app/tomcat9/logs 28 GB
/app/dspace7/log 67 GB
Aunque esto no causaba directamente el error, sí evidenciaba la necesidad de implementar o revisar políticas de rotación, compresión y retención de logs.
DSpace utiliza un core de Solr específico para OAI. Por ello, la siguiente hipótesis fue que el core:
Consultamos el estado de los cores:
curl -sS \
"http://127.0.0.1:8983/solr/admin/cores?action=STATUS&wt=json"
El core oai se encontraba operativo:
"oai": {
"name": "oai",
"numDocs": 47847,
"deletedDocs": 4708,
"current": true,
"size": "328.99 MB"
}
Además:
"initFailures": {}
Esto permitió descartar:
La configuración de DSpace indicaba:
oai.enabled = true
oai.path = oai
oai.storage = solr
oai.url = ${dspace.server.url}/${oai.path}
oai.solr.url = ${solr.server}/${solr.multicorePrefix}oai
oai.cache.enabled = true
oai.cache.dir = ${dspace.dir}/var/oai
El último punto fue especialmente importante:
oai.cache.enabled = true
oai.cache.dir = ${dspace.dir}/var/oai
DSpace utilizaba una caché en:
/app/dspace7/var/oai
A partir de ese momento, la investigación se concentró en los permisos de ese directorio.
Al revisar el contenido de la caché OAI encontramos:
ls -lah /app/dspace7/var/oai
Resultado:
drwxr-xr-x 3 dspace dspace 4.0K Apr 13 18:17 .
-rw-rw-r-- 1 dspace dspace 16 Jul 28 01:00 date.file
drwxr-x--- 2 root root 204K Jun 30 16:47 requests
El directorio:
/app/dspace7/var/oai/requests
pertenecía a:
root:root
y tenía permisos:
750
Sin embargo, Tomcat estaba ejecutándose como:
dspace:dspace
Esto se confirmó con:
ps -eo user,group,pid,cmd \
| grep '[o]rg.apache.catalina.startup.Bootstrap'
Por lo tanto, el proceso Java de DSpace no podía:
requests.Esa era la causa directa del mensaje:
An internal read or write operation failed
El directorio contenía cientos de megabytes de respuestas OAI almacenadas:
total 437M
Los archivos también pertenecían a root:root:
-rw-r----- 1 root root 564K ...
-rw-r----- 1 root root 602K ...
-rw-r----- 1 root root 570K ...
La explicación más probable es que en algún momento se ejecutó un comando administrativo de DSpace como usuario root, por ejemplo:
/app/dspace7/bin/dspace oai ...
Al ejecutar la tarea como root, los archivos y directorios creados quedaron bajo ese propietario.
Posteriormente, Tomcat, que corre como usuario dspace, perdió acceso a la caché.
Se corrigió el propietario del árbol OAI:
chown -R dspace:dspace /app/dspace7/var/oai
También se dejaron permisos restrictivos pero funcionales:
find /app/dspace7/var/oai -type d -exec chmod 750 {} \;
find /app/dspace7/var/oai -type f -exec chmod 640 {} \;
chmod 755 /app/dspace7/var/oai
Luego se comprobó que el usuario dspace
sudo -u dspace sh -c '
touch /app/dspace7/var/oai/requests/prueba_permiso &&
rm -f /app/dspace7/var/oai/requests/prueba_permiso
'
La prueba terminó correctamente.
No fue necesario reiniciar Tomcat.
Se volvió a consultar el endpoint directamente:
curl -i \
"http://127.0.0.1:8080/server/oai/request?verb=Identify"
La respuesta fue:
HTTP/1.1 200
Content-Type: text/xml;charset=UTF-8
El XML OAI-PMH volvió a generarse correctamente:
<Identify>
<repositoryName>Repositorio infodi</repositoryName>
<baseURL>
https://repositorio.infodi.cl/server/oai/request
</baseURL>
<protocolVersion>2.0</protocolVersion>
<adminEmail>repositorio@infodi.cl</adminEmail>
<earliestDatestamp>2013-03-05T17:45:02Z</earliestDatestamp>
<deletedRecord>transient</deletedRecord>
<granularity>YYYY-MM-DDThh:mm:ssZ</granularity>
</Identify>
La interfaz visual del proveedor OAI también volvió a funcionar. Al acceder sin indicar un verbo:
https://repositorio.infodi.cl/server/oai/request
DSpace mostró correctamente:
Error: Illegal verb
Este mensaje es esperado, ya que una solicitud OAI-PMH debe incluir un verbo válido como Identify, ListSets, ListRecords o ListIdentifiers.
Luego de resolver el error interno, un cosechador externo todavía podía mostrar:
Invalid URL (403 Forbidden)
Es importante distinguir ambos problemas.
El error 500 estaba dentro del backend de DSpace y era causado por permisos incorrectos en la caché OAI.
Un 403 indica que la solicitud fue rechazada por una capa de acceso o seguridad. Las causas posibles incluyen:
Un problema de certificado SSL normalmente no produce directamente un HTTP 403. Generalmente genera mensajes como:
SSL certificate problem
infodile to get local issuer certificate
certificate verify failed
TLS handshake failed
Sin embargo, en una arquitectura con Cloudflare también debe considerarse que el servidor de origen puede utilizar un certificado Cloudflare Origin CA.
Ese certificado está diseñado para la conexión:
Cloudflare → servidor de origen
No es un certificado de confianza pública para navegadores o cosechadores que se conecten directamente al origen.
Por lo tanto, si un cliente evita Cloudflare y accede directamente al servidor, puede presentar problemas de validación SSL. No obstante, si recibe específicamente un 403, debe revisarse la capa de seguridad y el registro de acceso.
Para analizar un 403 conviene solicitar al cosechador:
Luego se puede buscar la solicitud en los logs:
grep "IP_DEL_COSECHADOR" /var/log/nginx/access.log
Si el servidor está detrás de Cloudflare, se debe registrar la IP real mediante encabezados como:
CF-Connecting-IP
También conviene revisar:
/var/log/nginx/error.log
/app/tomcat9/logs/localhost_access_log.YYYY-MM-DD.txt
Los comandos administrativos deben ejecutarse como el usuario propietario de la instalación:
sudo -u dspace /app/dspace7/bin/dspace ...
Evitar:
root@servidor:~# /app/dspace7/bin/dspace ...
find /app/dspace7/var \
-user root \
-printf '%M %u:%g %p\n'
También:
find /app/dspace7/log \
-user root \
-printf '%M %u:%g %p\n'
sudo -u dspace test -w /app/dspace7/var/oai/requests \
&& echo "Directorio escribible" \
|| echo "Sin permiso de escritura"
En este caso se detectaron decenas de gigabytes de logs históricos. Es recomendable implementar logrotate para:
Se puede implementar una verificación automática:
curl -fsS \
"https://repositorio.infodi.cl/server/oai/request?verb=Identify" \
-o /dev/null
Si el comando devuelve un código distinto de cero, se puede generar una alerta.
curl -fsS \
"http://127.0.0.1:8983/solr/oai/admin/ping?wt=json"
# Probar OAI públicamente
curl -sk \
"https://repositorio.infodi.cl/server/oai/request?verb=Identify"
Probar directamente contra Tomcat
curl -i \
"http://127.0.0.1:8080/server/oai/request?verb=Identify"
Revisar espacio
df -h /
df -h /app
df -i /
Verificar proceso de Tomcat
ps -eo user,group,pid,cmd \
| grep '[o]rg.apache.catalina.startup.Bootstrap'
Verificar cores de Solr
curl -sS \
"http://127.0.0.1:8983/solr/admin/cores?action=STATUS&wt=json"
Revisar configuración OAI
grep -RniE \
"oai.|xoai|solr.*oai|baseUrl|baseURL" \
/app/dspace7/config
Revisar permisos de la caché
ls -lah /app/dspace7/var/oai
ls -lah /app/dspace7/var/oai/requests | head
Corregir propietarios
chown -R dspace:dspace /app/dspace7/var/oai
Probar escritura
sudo -u dspace sh -c '
touch /app/dspace7/var/oai/requests/prueba_permiso &&
rm -f /app/dspace7/var/oai/requests/prueba_permiso
'
El error HTTP 500 del servicio OAI-PMH no estaba relacionado con Angular, Cloudflare, Nginx, SSL, falta de disco ni con un core OAI de Solr dañado.
La causa fue un problema de propiedad y permisos en:
/app/dspace7/var/oai/requests
El directorio había sido creado o modificado como root:root, mientras que DSpace se ejecutaba como dspace:dspace.
Al corregir el propietario, el endpoint volvió inmediatamente a responder con HTTP 200.
Este incidente demuestra la importancia de no ejecutar tareas administrativas de DSpace como root y de revisar siempre el usuario efectivo de Tomcat antes de modificar índices, cachés, logs o directorios de trabajo.
También muestra que los errores visibles en Tomcat no siempre son la causa original. En este caso, getOutputStream() has already been called era solo una excepción secundaria que ocultaba el verdadero problema: DSpace no podía acceder a su propia caché OAI.